I would like to inject a Mockito mock object into a Spring (3+) bean for the purposes of unit testing with JUnit. My bean dependencies are currently injected by using the
I use a combination of the approach used in answer by Markus T and a simple helper implementation of ImportBeanDefinitionRegistrar
that looks for a custom annotation (@MockedBeans
) in which one can specify which classes are to be mocked. I believe that this approach results in a concise unit test with some of the boilerplate code related to mocking removed.
Here's how a sample unit test looks with that approach:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ExampleServiceIntegrationTest {
//our service under test, with mocked dependencies injected
@Autowired
ExampleService exampleService;
//we can autowire mocked beans if we need to used them in tests
@Autowired
DependencyBeanA dependencyBeanA;
@Test
public void testSomeMethod() {
...
exampleService.someMethod();
...
verify(dependencyBeanA, times(1)).someDependencyMethod();
}
/**
* Inner class configuration object for this test. Spring will read it thanks to
* @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class.
*/
@Configuration
@Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration
@MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked
static class ContextConfiguration {
@Bean
public ExampleService exampleService() {
return new ExampleService(); //our service under test
}
}
}
To make this happen you need to define two simple helper classes - custom annotation (@MockedBeans
) and a custom
ImportBeanDefinitionRegistrar
implementation. @MockedBeans
annotation definition needs to be annotated with @Import(CustomImportBeanDefinitionRegistrar.class)
and the ImportBeanDefinitionRgistrar
needs to add mocked beans definitions to the configuration in it's registerBeanDefinitions
method.
If you like the approach you can find sample implementations on my blogpost.
Today I found out that a spring context where I declared a before the Mockito beans, was failing to load. After moving the AFTER the mocks, the app context was loaded successfully. Take care :)
For the record, all my tests correctly work by just making the fixture lazy-initialized, e.g.:
<bean id="fixture"
class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer"
lazy-init="true" /> <!-- To solve Mockito + Spring problems -->
<bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" />
<bean id="applicationMessageBus"
class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="it.tidalwave.messagebus.MessageBus" />
</bean>
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="javax.servlet.ServletContext" />
</bean>
I suppose the rationale is the one Mattias explains here (at the bottom of the post), that a workaround is changing the order the beans are declared - lazy initialization is "sort of" having the fixture declared at the end.
I can do the following using Mockito:
<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.abcd.StateMachine"/>
</bean>
Since 1.8.3 Mockito has @InjectMocks
- this is incredibly useful. My JUnit tests are @RunWith
the MockitoJUnitRunner
and I build @Mock
objects that satisfy all the dependencies for the class being tested, which are all injected when the private member is annotated with @InjectMocks
.
I @RunWith
the SpringJUnit4Runner
for integration tests only now.
I will note that it does not seem to be able to inject List<T>
in the same manner as Spring. It looks only for a Mock object that satisfies the List
, and will not inject a list of Mock objects. The workaround for me was to use a @Spy
against a manually instantiated list, and manually .add the mock object(s) to that list for unit testing. Maybe that was intentional, because it certainly forced me to pay close attention to what was being mocked together.
I developed a solution based on the proposal of Kresimir Nesek. I added a new annotation @EnableMockedBean in order to make the code a bit cleaner and modular.
@EnableMockedBean
@SpringBootApplication
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockedBeanTest.class)
public class MockedBeanTest {
@MockedBean
private HelloWorldService helloWorldService;
@Autowired
private MiddleComponent middleComponent;
@Test
public void helloWorldIsCalledOnlyOnce() {
middleComponent.getHelloMessage();
// THEN HelloWorldService is called only once
verify(helloWorldService, times(1)).getHelloMessage();
}
}
I have written a post explaining it.