Autowiring of beans generated by EasyMock factory-method?

matsev picture matsev · Jun 14, 2011 · Viewed 12.8k times · Source

I have a problem that seems really strange to me. I have the following setup:

An interface:

package com.example;

public interface SomeDependency {
}

A spring component:

package com.example;

@Component
public class SomeClass {
}

A spring test config with a mocked bean generated by EasyMock:

<beans ....>
    <context:component-scan base-package="com.example"/>

    <bean id="someInterfaceMock" class="org.easymock.EasyMock" factory-method="createMock">
        <constructor-arg value="com.example.SomeDependency" />
    </bean> 
</beans>

And a unit test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testconfig.xml")
public class SomeClassTest {

    @Autowired
    SomeClass someClass;

    @Autowired
    SomeDependency someDependency;

    @Test
    public void testSomeClass() throws Exception {
        assertNotNull(someClass);
    }

    @Test
    public void testSomeDependency() throws Exception {
        assertNotNull(someDependency);
    }
}

The project compiles and the tests pass without any problem, i.e. autowiring of both SomeClass (a "real" object) and SomeDependency (a mock object generated by EasyMock) succeed.

However, if I change the implementation of SomeClass to:

@Component
public class SomeClass {

    @Autowired
    SomeDependency someDependency;
}

both tests fail because

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.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)}

So my questions are:

  1. Why does Spring fail to autowire the dependency to SomeClass (when it succeeds autowiring the same dependency to SomeClassTest)?
  2. How can I change the SomeClassTest or testconfig.xml to make the tests pass?

Comment: In reality the class represented by SomeClass is part of a framework. Consequently, it cannot easily be updated, at least not within reasonable time.

Dependencies:

  • Spring: 3.0.5.RELEASE
  • EasyMock: 3.0

Edit:

As of Spring 3.2 RC1, the problem with generic factory methods and mock objects has been solved.

/Mattias

Answer

Wilhelm Kleu picture Wilhelm Kleu · Jun 14, 2011

It seems the order of the definitions in the xml actually matter when using factories to create beans with autowiring. If you place the declaration of someInterfaceMock above component-scan it will work.

Some clarification why: When Spring tries to autowire SomeClass it searches for a bean of type SomeDependency. At this stage someInterfaceMock is still a factory so Spring checks the signature of the factory method EasyMock.createMock(...) which returns <T> so Spring only finds an Object which isn't the type required.

A better way would be to use Spring's FactoryBean interface to create your mocks.

Here is a basic implementation that should work:

public class EasyMockFactoryBean<T> implements FactoryBean<T> {
    private Class<T> mockedClass;

    public void setMockedClass(Class mockedClass) {
        this.mockedClass = mockedClass;
    } 

    public T getObject() throws Exception {
        return EasyMock.createMock(mockedClass);
    }

    public Class<T> getObjectType() {
        return mockedClass;
    }

    public boolean isSingleton() {
        return true;
    } 

}

Here is the bean definition (the order won't matter!):

<bean class="com.example.EasyMockFactoryBean">
    <property name="mockedClass" value="com.example.Dependancy"/>
</bean>