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


当前回答

今天,我发现在Mockito bean之前声明的spring上下文无法加载。 移动After模拟后,应用程序上下文被成功加载。 保重:)

其他回答

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

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

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

如果您正在使用spring >= 3.0,请尝试使用spring @Configuration注释来定义应用程序上下文的一部分

@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {

    @Bean
    public ApplicationService applicationService() {
        return mock(ApplicationService.class);
    }

}

如果你不想使用@ImportResource,也可以用另一种方式:

<beans>
    <!-- rest of your config -->

    <!-- the container recognize this as a Configuration and adds it's beans 
         to the container -->
    <bean class="com.package.DaoTestConfiguration"/>
</beans>

有关更多信息,请参阅spring-framework-reference:基于java的容器配置

更新-新的答案在这里:https://stackoverflow.com/a/19454282/411229。这个答案只适用于3.2之前的Spring版本。

我一直在寻找一个更明确的解决方案。这篇博客文章似乎涵盖了我的所有需求,并且不依赖于bean声明的顺序。这一切都要归功于Mattias Severson。http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/

基本上,实现一个FactoryBean

package com.jayway.springmock;

import org.mockito.Mockito;
import org.springframework.beans.factory.FactoryBean;

/**
 * A {@link FactoryBean} for creating mocked beans based on Mockito so that they 
 * can be {@link @Autowired} into Spring test configurations.
 *
 * @author Mattias Severson, Jayway
 *
 * @see FactoryBean
 * @see org.mockito.Mockito
 */
public class MockitoFactoryBean<T> implements FactoryBean<T> {

    private Class<T> classToBeMocked;

    /**
     * Creates a Mockito mock instance of the provided class.
     * @param classToBeMocked The class to be mocked.
     */
    public MockitoFactoryBean(Class<T> classToBeMocked) {
        this.classToBeMocked = classToBeMocked;
    }

    @Override
    public T getObject() throws Exception {
        return Mockito.mock(classToBeMocked);
    }

    @Override
    public Class<?> getObjectType() {
        return classToBeMocked;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

接下来更新你的spring配置如下:

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

    <bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean">
        <constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" />
    </bean>
</beans>

如果您正在使用Spring Boot 1.4,它有一种很棒的方式来实现这一点。只需要在你的类上使用新品牌@SpringBootTest,在字段上使用@MockBean, SpringBoot就会创建一个这种类型的mock,并将它注入到上下文中(而不是注入原来的那个):

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {

    @MockBean
    private RemoteService remoteService;

    @Autowired
    private Reverser reverser;

    @Test
    public void exampleTest() {
        // RemoteService has been injected into the reverser bean
        given(this.remoteService.someCall()).willReturn("mock");
        String reverse = reverser.reverseSomeCall();
        assertThat(reverse).isEqualTo("kcom");
    }

}

另一方面,如果你没有使用Spring Boot,或者你使用的是以前的版本,你将不得不做更多的工作:

创建一个@Configuration bean,将模拟对象注入Spring上下文:

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}

使用@Primary注释可以告诉spring,如果没有指定限定符,则该bean具有优先级。

确保您用@Profile(“useMocks”)注释了类,以便控制哪些类将使用模拟,哪些类将使用真正的bean。

最后,在你的测试中,激活userMocks配置文件:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
@ActiveProfiles(profiles={"useMocks"})
public class YourIntegrationTestIT {

    @Inject
    private MyBean myBean; //It will be the mock!


    @Test
    public void test() {
        ....
    }
}

如果你不想使用mock而是真正的bean,就不要激活useMocks配置文件:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
public class AnotherIntegrationTestIT {

    @Inject
    private MyBean myBean; //It will be the real implementation!


    @Test
    public void test() {
        ....
    }
}

我根据Kresimir Nesek的建议开发了一个解决方案。我添加了一个新的注释@EnableMockedBean,以使代码更加清晰和模块化。

@EnableMockedBean
@SpringBootApplication
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockedBeanTest.class)
public class MockedBeanTest {

    @MockedBean
    private HelloWorldService helloWorldService;

    @Autowired
    private MiddleComponent middleComponent;

    @Test
    public void helloWorldIsCalledOnlyOnce() {

        middleComponent.getHelloMessage();

        // THEN HelloWorldService is called only once
        verify(helloWorldService, times(1)).getHelloMessage();
    }

}

我已经写了一篇文章来解释它。