DatumEdge The website of James Shaw

Using JMock with Spring 3 tests

22 October 2012

Spring framework comes with a module, Spring Testing, that integrates with JUnit 4. It allows us to test Spring beans by wiring them into a JUnit test class. If we are writing an integration test, we may want to mock the dependencies of the bean under test. This article shows how to include those mock objects in a Spring ApplicationContext.

As an alternative to XML configuration or classpath scanning, Spring recently added support for Java-based configuration. We'll take this approach in our example application.

Let's step through the test:
@RunWith(SpringJUnit4ClassRunner.class)
This JUnit 4 annotation lets Spring Testing hook into the testing lifecycle.
@ContextConfiguration(classes={MyServiceConfig.class, MyServiceTest.Config.class})
Instructs Spring to create an ApplicationContext containing beans loaded from two configuration classes: MyServiceConfig and MyServiceTest.Config. The order in which these classes are declared is important: a bean definition from MyServiceTest.Config will overwrite a bean definition MyServiceConfig if they share the same bean name.
@DirtiesContext(classMode=AFTER_EACH_TEST_METHOD)
Causes Spring to create a new ApplicationContext for each test method. Since mockeries are stateful, we need to dirty the ApplicationContext to ensure that we have a fresh Mockery for each test method.
@Configuration public static class Config
In this configuration class we define our mockery and any collaborators that we want to mock.
@Bean public MyCollaborator myCollaborator()
This bean definition shares the same bean name as MyCollaboratorConfig.myCollaborator(). When the test runs, you will see a message similar to this, confirming that our mock collaborator replaces the production collaborator:
INFO: Overriding bean definition for bean 'myCollaborator':
replacing
  [Root bean:
    factoryBeanName=myCollaboratorConfig;
    factoryMethodName=myCollaborator;
    defined in uk.co.datumedge.springmock.MyCollaboratorConfig]
with
  [Root bean:
    factoryBeanName=myServiceTest.Config;
    factoryMethodName=myCollaborator;
    defined in uk.co.datumedge.springmock.test.MyServiceTest$Config]
@Rule @Autowired public JUnitRuleMockery context
Spring autowires the mockery bean defined in MyServiceTest.Config. The @Rule annotation is similar to @RunWith, allowing JMock to hook into JUnit's testing lifecycle.
Now we can autowire the class under test, MyService, and its mocked collaborator. The remainder of the test class is written in the same style as a normal unit test.

Spring Java configuration gotchas

In Spring's Java-based configuration, @Configuration-annotated classes are themselves Spring beans. This allows us to wire them into other configuration classes and invoke their methods to wire beans together:

How is it that our test uses the mock collaborator, when we can see our production collaborator being wired in this configuration class? To explain, we need to understand some of the pixie dust that Spring sprinkles over our source:

Invoking a @Bean-annotated method will return a bean that matches the method's return type and method name. This is usually the instance returned by the method in the source code, unless that bean definition has been overridden.

In our example application, MyServiceTest.Config.myCollaborator() has overridden MyCollaboratorConfig.myCollaborator(). Calling either method will return the same, mock object.

Resources