Liz Douglass

On mocking and Spring

with one comment

Two weeks ago I was pairing with Alex on some Java integration tests for an application that uses Spring for dependency injection. Whilst writing one of the tests we realised that we needed to mock some behaviour on one of our Spring configured classes. Instinctively the simplest way to do this would be to replace the bean in our application context with another one, but alas after some brief investigation we discovered this is not an option.We ended up using the same pattern that was already in use in our code base, and I’m sure it’s one that some people will be familiar with:

First up we extracted an interface from the class that whose behaviour we wanted to mock:

public interface FooRepository {
    Foo loadFor(XYZ xyz);
}

Next we created a new Application Context XML configuration file to use for the test and included the ‘real’ one from our application. We also defined a stub class that implemented our new interface. It also had the same id as the class that we wanted to mock behaviour for meaning that the pre-existing definition would be overridden:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="FooRepository" class="FooRepositoryStub"/>
 <import resource="applicationContext.xml"/>
</beans>

Now for the behaviour that we want in the test…. Singleton scoped Spring beans are loaded at start-up (unless you have lazy loading which we do not). Hence if we want to be able to define specific behaviour inside a test we can’t use on our stub implementation alone – rather we need to use the stub as a wrapper for a delegate:

public class FooRepositoryStub implements FooRepository {

    private FooRepository delegate;

    public Foo loadFor(XYZ xyz) {
        return delegate.loadFor(xyz);
    }

    public void setDelegate(FooRepository delegate) {
        this.delegate = delegate;
    }
}

The delegate can then be used to provide the mocking that we wanted all along:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-test.xml")

public class ValidationIntegrationTest {

    @Autowired
    private FooRepository FooRepository;
    private FooRepository FooRepositoryDelegate = mock(FooRepository.class);

    @Before
    public void initializeRepositoryDelegates() {
        ((FooRepositoryStub) FooRepository).setDelegate(FooRepositoryDelegate);
    }

    @Test
    public void shouldInvalidateXYZWhenTheABCRuleIsViolated() {
        XYZ xyz = givenSomeContext();
        andTheUserHoldsFoo(xyz);
        whenSomethingHappends(xyz);
        thenItShouldFailWithReasons(xyz, "XYZ failed because of the ABC rule was violated");
    }

    private void andTheUserHoldsFoo(XYZ xyz) {
        Foo foo = mock(Foo.class);
        when(FooRepositoryDelegate.loadFor(xyz)).thenReturn(foo);
    }
    ....
}

So, with only an interface, another ApplicationContext XML file, a stub and a mock, we can define the behaviour in the test that we wanted (phew)! I’ve used PicoContainer before and only just now realise how easy with had it in setting up tests. Of course there may be an easier way to do all this – and if you have one then I’d love to hear from you.

Advertisements

Written by lizdouglass

August 20, 2009 at 9:47 pm

Posted in Java

Tagged with , , ,

One Response

Subscribe to comments with RSS.

  1. As my colleague Julio quite rightly pointed out to me, it is not favourable to use mocking in integration tests. I agree with him entirely and do not want to encourage doing so. However, I would like to add that the situation I described in the post was mocking a repository lookup/find. Our motivation for this came about as we are not the owners of the database that the repository is using. Therefore we were not able to change the data in it for testing. This is obviously not ideal and we have since found another means of getting the data that avoids having to do this.

    lizdouglass

    August 27, 2009 at 9:56 pm


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: