我想将Mockito模拟对象注入到Spring (3+) bean中,以便使用JUnit进行单元测试。我的bean依赖项目前是通过在私有成员字段上使用@Autowired注释注入的。

我考虑过使用ReflectionTestUtils。setField,但是我希望注入的bean实例实际上是一个代理,因此没有声明目标类的私有成员字段。我不希望为依赖项创建公共setter,因为我将纯粹为了测试目的而修改我的接口。

我遵循了Spring社区提供的一些建议,但模拟没有被创建,自动连接失败:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

我目前遇到的错误如下:

...
Caused by: org...NoSuchBeanDefinitionException:
    No matching bean of type [com.package.Dao] found for dependency:
    expected at least 1 bean which qualifies as autowire candidate for this dependency.
    Dependency annotations: {
        @org...Autowired(required=true),
        @org...Qualifier(value=dao)
    }
at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.java:901)
at org...DefaultListableBeanFactory.doResolveDependency(D...y.java:770)

如果我将constructor-arg值设置为无效的值,那么在启动应用程序上下文时就不会出现错误。


当前回答

更新:现在有更好、更清洁的解决方案来解决这个问题。请先考虑其他答案。

我最终在ronen的博客上找到了答案。我遇到的问题是由于Mockito方法。mock(类c)声明Object的返回类型。因此,Spring无法从工厂方法返回类型推断出bean类型。

Ronen的解决方案是创建一个返回模拟的FactoryBean实现。FactoryBean接口允许Spring查询工厂bean创建的对象类型。

我的模拟bean定义现在看起来如下:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

其他回答

从Spring 3.2开始,这不再是一个问题。Spring现在支持通用工厂方法结果的自动装配。请参阅本博客文章中“通用工厂方法”一节:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/。

重点是:

在Spring 3.2中,工厂方法的泛型返回类型现在是 正确地推断,并按类型自动装配模拟应该工作为 预期。因此,自定义变通方法,如 MockitoFactoryBean、EasyMockFactoryBean或springckito可能不是 不再必要。

这意味着这应该是开箱即用的:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

为了记录,我所有的测试都是通过使fixture惰性初始化来正确工作的,例如:

<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>

我认为基本原理是Mattias在这里(在文章的底部)解释的,一种变通方法是改变bean的声明顺序——延迟初始化是“某种程度上”在最后声明fixture。

我可以使用Mockito做以下事情:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>

也许不是完美的解决方案,但我倾向于不使用spring来进行单元测试的DI。单个bean(被测试的类)的依赖关系通常不会太复杂,所以我只是直接在测试代码中进行注入。

如果你正在使用spring boot 2.2+,你可以使用@MockInBean作为@MockBean的替代,并保持spring上下文干净:

@SpringBootTest
public class MyServiceTest {

    @MockInBean(MyService.class)
    private ServiceToMock serviceToMock;

    @Autowired
    private MyService myService;

    @Test
    public void test() {
        Mockito.when(serviceToMock.returnSomething()).thenReturn(new Object());
        myService.doSomething();
    }
}

免责声明:我创建这个库是为了避免由@MockBean/@SpringBean导致的Spring上下文重新创建,从而导致构建测试阶段变慢(参见在测试中使用@MockBean强制重新加载应用程序上下文或@MockBean的问题)