All 1 entries tagged Web
View all 68 entries tagged Web on Warwick Blogs | View entries tagged Web at Technorati | There are no images tagged Web on this blog
February 13, 2008
Unit testing Spring AOP
This is an article for developers of Spring applications, who are looking at using its AOP support but not sure how to test it. If you’re not one of these people, go and read an interesting book instead. It also assumes you’re fairly proficient at Spring in general, but do leave a comment if something isn’t clear.
What is?
When we write Spring applications at work we like to have lots of unit tests, because they’re easy to do and make it red-flashing-light-obvious if something gets broken. Another cool thing you can do with Spring is Aspect Oriented Programming, allowing you to say things like “whenever an import() method is called anywhere in the code, do some logging”, without clogging up the actual import() method with logging code. This is great, but since AOP is a kind of magic voodoo on top of regular Java, you can’t just write a regular unit test and expect the advice to be applied. With logging you might sigh and continue with your work, but when you have AOP advice that checks something important like security permissions, you need to know that it’s actually going to do the check.
Explain again how sheep’s bladders may be employed to prevent earthquakes
Here’s an example applicationContext.xml with an advice bean that describes the aspect, and the autoproxy declaration that gets the advice applied (some package names may be fictional):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" default-autowire="byName"> <!-- this is what makes the magic happen -- > <aop:aspectj-autoproxy/> <!-- this is what describes what the magic is -- > <bean id="exceptionHandlingAspect" class="uk.ac.warwick.insanoflex.ExceptionHandlingAspect" /> <!-- all your other beans here -- > </beans>
The ExceptionHandlingAspect, which wants to send an email when an exception gets thrown in certain methods, looks a bit like this:
@Aspect public class ExceptionHandlingAspect { //pointcut matching all import() methods in our package space @Pointcut("execution(* uk.ac.warwick..*.import(..))") public void inGroupsImporter() {} @AfterThrowing(pointcut="inGroupsImporter()", throwing="ex") public void handleException(Exception ex) { sendEmailOrSomething() //whatever you want to do, really. } }
Actually testing
Enough guff, how to test it? The easiest (!) way is to use the Spring-provided AbstractSingleSpringContextTests, or one of its subclasses. This instantiates a whole ApplicationContext containing all your beans, including the autoproxying object, and that’s what wraps the relevant beans in an advice-laden proxy. You just have to specify the XML configuration file to load. It’s useful for general integration testing, making sure the individual parts are wired up correctly.
I’d recommend splitting up your config into smaller files and use import, then you can have a special test configuration file that just loads the stuff you’ll need to test, and uses in-memory data sources like HQLDB instead of a live database. Doing all this isn’t the topic of this article though, and there’s plenty of documentation for application context testing in general on the Spring website.
But how to check that the aspect is applied? Rather than test for its existence, you need to test for its effect. You need to do two things: make the import method throw an exception, and check that the emailer’s send method is called. Since you’re testing the aspect and not your importer or emailer, you can simply use pretend objects here, and put them in your test configuration file. As usual, things are much easier if your objects implement an interface so we’ll assume that’s happened here.
class MockImporter implements Importer { public void import() { throw new RuntimeException("oh dear me"); } } class MockEmailer implements Emailer { private boolean sent; public boolean isSent() { return sent; } public void sendMail() { sent = true; } }
Then we can test.
public class ExceptionMailerTest extends AbstractDependencyInjectionSpringContextTests { protected String[] getConfigLocations() { //your test spring config, containing definitions of our fake importer and mailer. return new String[] { "classpath:test-app-context.xml" }; } public void testExceptionIsMailed() { //grab the beans from the context MockEmailer emailer = (MockEmailer)getApplicationContext().getBean('emailer'); MockImporter importer = (MockImporter)getApplicationContext().getBean('importer'); assertFalse(emailer.isSent()); try { importer.import(); } catch (RuntimeException e) { //good; we still want the exception to get thrown } assertTrue(emailer.isSent()); } }
As long as the advice is being applied correctly, the mailer.send() method will have been called.
Improvements?
It’s a lot of effort to get a whole custom application context set up just to test one aspect (though you’re likely to reuse it to test other things). It would be good if it were possible to run Spring’s AOP proxying code more directly, though I think it depends on running inside an ApplicationContext; a manually-created context that didn’t use XML might possibly make things simpler, especially if it let us use JMock to make mock objects instead of implementing them manually. If I find a better method, I’ll report it here.
Eclipse AspectJ Plugin
Even if you’re not using true AspectJ compiling, it’s still really useful to have the AspectJ plugin for Eclipse (if you’re using Eclipse, that is). The annotations that Spring uses are actually just the AspectJ ones, so AspectJ can understand them just as well. It tells you straight away whether your advice is going to apply to the right things. The only thing to keep in mind is that true AspectJ can weave virtually anything, whereas Spring’s proxy AOP can only apply to public methods on Spring beans, so something that shows up here won’t necessarily work in Spring unless you are using the AspectJ weaver.
The plugin helped here as it found where my exception handler wasn’t being applied; I had Exception rather than RuntimeException, so it would only match methods which explicitly declared “throws Exception”.
