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


当前回答

看看springingckito的开发速度和开放问题的数量,我现在有点担心把它引入我的测试套件堆栈。事实上,上一个版本是在Spring 4发布之前发布的,这带来了一些问题,比如“是否可以轻松地将它与Spring 4集成?”我不知道,因为我没试过。如果我需要在集成测试中模拟Spring bean,我更喜欢纯Spring方法。

有一个选项可以使用简单的Spring特性来伪造Spring bean。你需要使用@Primary, @Profile和@ActiveProfiles注解。我就这个话题写了一篇博客。

其他回答

如果您正在使用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() {
        ....
    }
}

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

如果你正在使用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的问题)

下面的代码使用自动装配-它不是最短的版本,但当它只适用于标准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> 

基于上述方法,我将给出几个例子

春天:

@ContextConfiguration(locations = { "classpath:context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService;
    @Mock
    private TestService2 testService2;
}

没有春天:

@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService = new TestServiceImpl();
    @Mock
    private TestService2 testService2;
}