Using JMock with Spring 3 tests
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
andMyServiceTest.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.
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:
- When a class is annotated with
@Configuration
, Spring generates a dynamic proxy class containing the mechanics needed to create bean definitions - When you invoke a method on a configuration class, you are really invoking a method on the generated proxy class
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.