Saturday, March 5, 2011

Spring @Autowired, JUnit and Mockito

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:
  1. Change the @ContextConfiguration annotation to move the file defining this bean (mockito.xml) in front of all others (very naive solution, but sometimes sufficient ;) )
  2. 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

3 comments:

  1. Well, I never had this problem, but I use a separate test-context.xml file for my unit tests. This one imports the "real" context file and then defines the mocked beans. I never thought deeper about it, but maybe this just works fine because I use the @Component annotation for almost every Spring bean to define it.

    In short: It works for me just by using @Component, @Autowired and the test xml file...

    ReplyDelete
  2. Thanks for the post, I never thought of wiring the Mocked class through XML config.

    Regards,
    Gordon Dickens

    twitter.com/gdickens
    linkedin.com/in/gordondickens
    Blog: technophile.gordondickens.com

    ReplyDelete
  3. I'd been fighting with this Mockito and @Autowired issue for a few seconds and finally found your post. This is exactly what I needed;) Thx!

    ReplyDelete