Spring Integration Tests, Part I, Creating Mock Objects
When writing integration tests with Spring, it can sometimes be convenient to mock one or more of Spring bean dependencies. However, during some circumstances strange things may happen… (In this post, Mockito has been used for creating mock objects, but the same problem applies to EasyMock as well. You can find the corresponding files if you follow the provided links.)
Test setup
In the following example the SomeClass
needs a reference to an instance of the SomeDependency
interface:
package com.jayway.example;
@Component
public class SomeClass {
@Autowired
private SomeDependency someDependency;
// more code...
}
package com.jayway.example;
public interface SomeDependency {
}
When writing the integration test, we use the static Mockito.mock(Class classToMock) method to create a mock instance. The straightforward approach would be to let Spring instantiate the mock object with a static factory method in the xml configuration, e.g.
<beans...>
<context:component-scan base-package="com.jayway.example"/>
<bean id="someDependencyMock" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.jayway.example.SomeDependency" />
</bean>
</beans>
(Corresponding EasyMock config.)
To verify that the beans are created and wired correctly, we write a simple test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("failing-mockito-config.xml")
public class BeanWiringTest {
@Autowired
SomeClass someClass;
@Autowired
SomeDependency someDependencyMock;
@Test
public void shouldAutowireDependencies() {
assertNotNull(someClass);
assertNotNull(someDependencyMock);
}
}
(Same test for EasyMock, just update the @ContextConfiguration
with the EasyMock config file.) To our surprise, the test fails:
[...]
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'someClass': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private com.jayway.example.SomeDependency com.jayway.example.SomeClass.someDependency;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [com.jayway.example.SomeDependency] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
[...]
Explanation
In order to find a solution to the problem, we need to understand Spring’s initialization process:
- Load bean definitions. This step includes scanning the application context XML files, scanning packages defined by
component-scan
, and loading the bean definitions found into the bean factory. (BeanFactoryPostProcessors such as the PropertyPlaceHolderConfigurer may be called to update the bean definitions.) - Instantiate beans and store them in the application context. The bean factory creates the bean from the bean definitions. Bean dependencies get injected.
- Bean post processing. All initializing methods (e.g. annotated with
@PostConstruct
,init-method
s declared in the XML and theafterPropertiesSet()
method will execute. Annotations such as@Required
will be validated. Dynamic proxies in an AOP environment will be created and so on.
The problem that we see occurs due to a corner case in the first two steps. When the bean definition is created Spring scans through the application context XML file. It finds the declaration of the mock bean and a reflective call is made to find out the return type of the method defined by the factory-method
declaration in an attempt to determine the bean’s type. The return type of this method is declared as the formal type parameter <T>
(remember that we use the
Mockito.mock(Class<T> classToMock) as factory method). Consequently, the bean definition of someDependencyMock
will be of java.lang.Object
and not SomeDependency
as one would think. In the second step, the someClass
bean is instantiated. Springs discovers the @Autowired
annotation and tries to fetch an existing SomeDependency
bean from the application context. None has yet been created, so the bean factory proceeds and looks for suitable bean definition in order to create an instance of the requested bean on the fly. This operation fails because the bean definition of someDependency
is mapped to java.lang.Object
. At this point, Spring bails out and throws the NoSuchBeanDefinitionException
.
FactoryBean to the rescue
The problem is solved by implementing a custom FactoryBean that can be used whenever we need to mock an object:
package com.jayway.springmock;
import org.mockito.Mockito;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} for creating mocked beans based on Mockito so that they
* can be {@link @Autowired} into Spring test configurations.
*
* @author Mattias Severson, Jayway
*
* @see FactoryBean
* @see org.mockito.Mockito
*/
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private Class<T> classToBeMocked;
/**
* Creates a Mockito mock instance of the provided class.
* @param classToBeMocked The class to be mocked.
*/
public MockitoFactoryBean(Class<T> classToBeMocked) {
this.classToBeMocked = classToBeMocked;
}
@Override
public T getObject() throws Exception {
return Mockito.mock(classToBeMocked);
}
@Override
public Class<?> getObjectType() {
return classToBeMocked;
}
@Override
public boolean isSingleton() {
return true;
}
}
(In the EasyMockFactoryBean, I have added an enum to define the type of mock object.)
Next, the Spring test config is updated to use the factory bean:
<beans...>
<context:component-scan base-package="com.jayway.example"/>
<bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean">
<constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" />
</bean>
</beans>
(Corresponding EasyMock config.) Spring will still instantiate the beans in the order that they were declared in the mockito-test-config.xml
, i.e. starting by creating an instance of SomeClass
and then attempt to inject an instance of a SomeDependency
into it before any has been created. The good news is that bean factory will scan through all declared FactoryBean
s, calls its getObjectType() method, and if the returned type is correct, call its getObject() which should return an instance of the requested type.
Alternative solution
There is another more brittle solution to the problem. By simply changing the order of the tags in the initial application context, e.g. putting the bean declaration that defines the mock above the component scan, will cause the test to pass. The bean definition will still be wrong in the sense that the someDependencyMock
will be defined as a java.lang.Object
, but there is another important difference. In this case, the bean factory will invoke the Mockito.mock(Class<T> classToMock) method with SomeDependency
as parameter causing the someDependencyMock
bean to be created and subsequently stored in the application context. Later, the someClass
bean is created and the dependency can obtained from the application context when requested by the bean factory and the wiring of the beans will succeed. However, making sure that the order of the bean declarations in the XML is always correct may neither be simple nor obvious, and it will get increasingly more difficult the more beans you add to the configuration.
Disclaimer
As stated in the in the first paragraph, I would only consider this solution for integration tests because of performance reasons. The application context itself adds some overhead, but the big performance hit is to create and wire all beans that are associated with it. When writing unit tests there is no need for this, so you could revert to constructor or setter dependency injection, Spring’s ReflectionTestUtils.setField(), PowerMock’s Whitebox.setInternalState(), Mockito’s @InjectMocks or similar techniques.
Acknowledgements
Thanks to Wilhelm Kleu at stack overflow for suggesting the solution and to Mattias Hellborg Arthursson for valuable discussions.
Dependencies
- Spring 3.0.6
- Mockito 1.8.5
- jUnit 4.10
- EasyMock 3.1
References
- Spring Reference Manual - Static Factory Method
- Spring Reference Manual - FactoryBean
- Spring Reference Manual - Integration Testing
- Mockito
- EasyMock
Next post: Spring Integration Tests, Part II, Using Mock Objects
Edit
As of Spring 3.2 RC1, the problem with generic factory methods and mock object has been solved.