我想将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值设置为无效的值,那么在启动应用程序上下文时就不会出现错误。


当前回答

下面的代码使用自动装配-它不是最短的版本,但当它只适用于标准spring/mockito jar时很有用。

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
   <property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean> 

其他回答

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

我最终在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>

我使用了Markus T在回答中使用的方法和ImportBeanDefinitionRegistrar的一个简单助手实现的组合,该实现查找一个自定义注释(@MockedBeans),可以在其中指定要模拟哪些类。我相信这种方法的结果是简洁的单元测试,删除了一些与模拟相关的样板代码。

下面是使用这种方法的单元测试示例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ExampleServiceIntegrationTest {

    //our service under test, with mocked dependencies injected
    @Autowired
    ExampleService exampleService;

    //we can autowire mocked beans if we need to used them in tests
    @Autowired
    DependencyBeanA dependencyBeanA;

    @Test
    public void testSomeMethod() {
        ...
        exampleService.someMethod();
        ...
        verify(dependencyBeanA, times(1)).someDependencyMethod();
    }

    /**
     * Inner class configuration object for this test. Spring will read it thanks to
     * @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class.
     */
    @Configuration
    @Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration
    @MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked
    static class ContextConfiguration {

        @Bean
        public ExampleService exampleService() {
            return new ExampleService(); //our service under test
        }
    }
}

要做到这一点,您需要定义两个简单的助手类——自定义注释(@MockedBeans)和自定义 ImportBeanDefinitionRegistrar实现。@MockedBeans注释定义需要使用@Import(CustomImportBeanDefinitionRegistrar.class)进行注释,并且ImportBeanDefinitionRgistrar需要在它的registerBeanDefinitions方法中将模拟bean定义添加到配置中。

如果你喜欢这种方法,你可以在我的博客上找到示例实现。

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

下面的代码使用自动装配-它不是最短的版本,但当它只适用于标准spring/mockito jar时很有用。

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
   <property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean> 

自从1.8.3 Mockito有了@InjectMocks——这是非常有用的。我的JUnit测试是@RunWith MockitoJUnitRunner,我构建了@Mock对象,它满足被测试类的所有依赖关系,当私有成员被@InjectMocks注释时,这些依赖关系都被注入。

我现在只使用SpringJUnit4Runner进行集成测试。

我将注意到,它似乎不能以与Spring相同的方式注入List<T>。它只寻找满足List的Mock对象,而不会注入Mock对象列表。我的解决方法是对一个手动实例化的列表使用@Spy,并手动将模拟对象添加到该列表中进行单元测试。也许这是故意的,因为它确实迫使我密切关注被嘲笑的是什么。