Blog

Testing with(out) aspects

26 Sep, 2007
Xebia Background Header Wave

Recently I wanted to add an aspect to some domain object, so that it was saved, the moment it changed state. However, after adding this aspect, the whole build of course failed, because a lot of the unit tests weren’t expecting the calls which were now woven into the domain object.

Of course I could alter all the unit tests so that they reflected the change in code. But this seems to defy the whole purpose of the aspect. Another option is of course postponing the weaving until after the unit tests. This didn’t seem to be the correct solution either, because I did want to write at least some tests to test that the aspect was doing its job.
So how did I get around this? The answer lay in Spring AOP. It shares much of the syntax of AspectJ, though it doesn’t (yet) offer all of the functionality. I’ve created a small sample for your reading pleasure.
At first we’ll look at a simple domain without aspects.
Victim.java

public class Victim {
    private int itemsInPosession;
    public Victim(int itemsInPosession) {
        this.itemsInPosession = itemsInPosession;
    }
    public void itemStolen() {
        if (itemsInPosession > 0) {
            itemsInPosession--;
        }
    }
    public int getItemsInPosession() {
        return itemsInPosession;
    }
}

And Thief.java

public class Thief {
    public void steal(Victim victim) {
        victim.itemStolen();
    }
}

Of course we have a unit test to test that the Thief indeed steals from a Victim:

public class ThievingTest extends TestCase {
    public void testShouldSucceedInStealing() {
        Victim victim = new Victim(1000);
        Thief thief = new Thief();
        thief.steal(victim);
        assertEquals(999, victim.getItemsInPosession());
    }
}

Running this test shows that the Thief succeeds in stealing an item from the Victim. But of course, we can’t let Thiefs randomly steal from Victims. We need to introduce a PoliceAgent who can put the Thief in Jail. Let’s add an interface to Jail, and write a new test asserting that the Thief is put in Jail for stealing.
Jail.java

public interface Jail {
    public void putInJail(Thief thief);
}

And the second unit test class:

public class ThievingJailTest extends TestCase {
    private Jail jail;
    public void setUp() {
        jail = EasyMock.createMock(Jail.class);
    }
    public void testShouldLandInJailForStealing() {
        Victim victim = new Victim(1000);
        Thief thief = new Thief();
        jail.putInJail(thief);
        EasyMock.replay(jail);
        thief.steal(victim);
        EasyMock.verify(jail);
        assertEquals(1000, victim.getItemsInPossession());
    }
}

Of course, this test fails, because there is no way to put the Thief in Jail yet. Let’s add the PoliceAgent as an aspect, because we want to intercept the Thief that is stealing:
PoliceAgentAspect.java

@Aspect
public class PoliceAgentAspect {
    private Jail jail;
    @Around("execution(void *Thief.steal(..))")
    public void arrestThief(ProceedingJoinPoint joinPoint) {
        Thief thief = (Thief) joinPoint.getThis();
        jail.putInJail(thief);
    }
    public void setJail(Jail jail) {
        this.jail = jail;
    }
}

In order for Spring AOP to notice the aspect, and do the wiring, we need to add an applicationContext.xml with the correct beans. In this simple example, the following suffices (I’ve stripped the namespace declarations for readability):


You can see that in the Spring context I’ve mocked the Jail. This code previously was in the unit test. Also I’ve added the Thief as a bean which can be injected. This ensures that Spring can intercept the Thief with the PoliceAgentAspect. The modified test looks like this:

public class ThievingJailTest extends AbstractDependencyInjectionSpringContextTests {
    private Jail jail;
    private Thief thief;
    @Override
    protected String[] getConfigLocations() {
        setAutowireMode(AUTOWIRE_BY_NAME);
        return new String[] {"applicationContext.xml"};
    }
    public void testShouldBePutInJailByPoliceAgentAspect() {
        Victim v = new Victim(1000);
        jail.putInJail(thief);
        EasyMock.replay(jail);
        thief.steal(v);
        EasyMock.verify(jail);
        assertEquals(1000, v.getItemsInPosession());
    }
    public void setJail(Jail jail) { this.jail = jail; }
    public void setThief(Thief thief) { this.thief = thief; }

When we now run this test it succeeds. Also our old test still runs perfectly and provides a green light. Using Spring AOP, we can ensure that our code works when not using aspects, and that the adding of aspects to our code gives the intended results.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts