Few days ago I've encountered interesting problem with autowiring
Mockito based
Spring Framework beans, let me share it with you :)
Everything started when I've made
JUnit test for some business logic code.
...
import static junit.framework.Assert.assertNotNull;
...
@ContextConfiguration({ "classpath:.../business-logic.xml", "classpath:.../orm-jpa.xml",
"classpath:.../test/mockito.xml", ... })
public class MockitoTest extends AbstractJUnit4SpringContextTests {
@Autowired
private EmployeeManager employeeManager;
@Test
public void test01() {
assertNotNull(employeeManager.get(Long.valueOf(1L)));
}
}
Default
EmployeeManager implementation used by me delegates the entity fetching to
EmployeeDAO:
@Component("business-logic.EmployeeManager")
public class DefaultEmployeeManager implements EmployeeManager {
@Autowired
private EmployeeDAO employeeDAO;
public Employee get(Long identifier) {
return employeeDAO.get(identifier);
}
}
EmployeeDAO in this example is a mock created using
Mockito:
<bean id="persistence.EmployeeDAO" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="....dao.EmployeeDAO" />
</bean>
When I tried to run the test, it responded with beautiful exception:
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'business-logic.EmployeeManager': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private ... .dao.EmployeeDAO ... .logic.impl.DefaultEmployeeManager.employeeDAO; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [... .dao.EmployeeDAO] 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)}
...
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private ... .dao.EmployeeDAO ... .logic.impl.DefaultEmployeeManager.employeeDAO; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [... .dao.EmployeeDAO] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
My first thoughts were - Why the heck it doesn't work?! There is a bean which can be used for autowiring! - but after few seconds I realized that I'm wrong :) - Here is the explanation why ;)
When
Spring Framework creates the beans, and tries to autowire the
EmployeeManager properties it seeks for the bean having class
EmployeeDAO, but at this point our
Mockito based bean is still a Factory, not concrete instance, therefore
Spring Framework checks the
mock method of our Factory, to determine the type of returned objects. As you may see below it has signature:
public static <T> T mock(Class<T> classToMock)
and thus the determined type is
Object, which of course doesn't match the desired one (
EmployeeDAO).
Let me read your mind at this point ;) - You think: What can we do with it? - Well, we have to assure that
EmployeeDAO bean will be turned into real instance before it will be requested for autowiring.
In other words
EmployeeDAO bean should be defined BEFORE the beans using it for autowiring.
We can do it in following ways:
- Change the @ContextConfiguration annotation to move the file defining this bean (mockito.xml) in front of all others (very naive solution, but sometimes sufficient ;) )
- Define for all beans referring EmployeeDAO that they depend on it
The last one can be achieved in following way:
@Component("business-logic.EmployeeManager")
@DependsOn("persistence.EmployeeDAO")
public class DefaultEmployeeManager implements EmployeeManager ...
when you define your beans using context scanning, or if you simple define them in XML, using depends-on attribute of the bean.
PS: Libraries/Frameworks used in the above example: Spring Framework - 3.0.4, Mockito - 1.8.5 and JUnit 4.8.1