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”.

aop

- One comment

  1. Alex

    Hey thank you for this post. It might need a bit updating, but it helped me a lot to find the right path. AbstractDependencyInjectionSpringContextTests is deprecated now, I used AbstractJUnit38SpringContextTests instead (for Spring 3.0.5). It also took me some time to realize, that my mock classes must be static if I want to declare them inside the test class like this:
    public static class MockImporter implements Importer {} and then have com.mypackage.TestClass.MockImporter as full classname (needed for the context.xml bean declaration)

    09 Oct 2014, 17:15


Add a comment

Name
Email
Anti-Spam Question
My t-shirt is red. What colour is my t-shirt?
Anti-Spam Answer
Comment


Your IP address will be recorded. -

You can not use HTML, but you can use our special markup -

Trackbacks

February 2008

Mo Tu We Th Fr Sa Su
Jan |  Today  | Mar
            1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29      

Search this blog

Tags

Galleries

Most recent comments

  • Hey thank you for this post. It might need a bit updating, but it helped me a lot to find the right … by Alex on this entry
  • Logging is another prime use for AOP good article. by Nick Howes on this entry
  • Hi, another basic AspectJ Spring example can be found here http://www.developers–blog.org/blog/defau… by Rafael on this entry
  • Thanks Jam Hug. Hope you're well. I deleted your three duplicate comments for you. by Nick Howes on this entry
  • that wasn't funny at all. your standards are slipping. by james hughes on this entry

Blog archive

Loading…
Not signed in
Sign in

Powered by BlogBuilder
© MMXIV