Unit testing Spring beans with mocked dependencies

When overriding dependencies of Spring beans with mock objects, all tests except the first will fail. This blog post explains why and provides a simple solution.



During development, sooner or later you’ll want to test the aspects that Spring adds to/around your code. After all, how else can you know that transactions are comitted and rolled back appropriately? Or that your own aspect is applied correctly? Whether you’re writing unit or integration tests, you’ll want to test it.


Of course, the mock objects you’ll be injecting during the test must be known to your test code. So at one point you’ll probably override your Spring context using the interface ApplicationContextAware:



@Override
public final void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.genericApplicationContext =
(GenericApplicationContext) applicationContext;
String beanName = “myService”;
Object instance = createMock(MyService.class);
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) genericApplicationContext.getBeanFactory();
if (beanFactory.containsSingleton(beanName)) {
beanFactory.destroySingleton(beanName);
} else if (beanFactory.containsBean(beanName)) {
beanFactory.destroyBean(beanName, beanFactory.getBean(beanName));
}
beanFactory.registerSingleton(beanName, instance);
}


But this is not sufficient: you’re changing the Spring context, and you want your test cases to be independent. So you add the annotation @DirtiesContext to your Spring testcase superclass.
Then then, lo and behold: your mock objects are corrrectly injected into your Spring beans.


For the first test. Not the second, nor the third, and so on…


What went wrong? (and a solution)



JUnit instantiates a test class instance per test method. So for the first test, a test class instance is created, and setApplicationContext is called. This overrides the dependencies, and the singleton bean is instantiated with the mocked dependencies. Perfect. But for the second test, the mock object created in setApplicationContext is not injected: the previously created singleton bean is used again. And thus your test fails.


The solution is extremely simple: create a constant (private static final field) for each mocked dependency. This ensures that all instances of the test case use the same mock object. Just ensure you reset() the mock objects for each test (which is a good practice anyway as the test method didn’t create it).

No comments:

Post a Comment