将Mockito mocks注入到Spring bean中

将Mockito mocks注入到Spring bean中,spring,dependency-injection,junit,annotations,mockito,Spring,Dependency Injection,Junit,Annotations,Mockito,为了使用JUnit进行单元测试,我想将Mockito mock对象注入到Spring(3+)bean中。我的bean依赖项目前是通过在私有成员字段上使用@Autowired注释注入的 我考虑过使用ReflectionTestUtils.setField,但我希望注入的bean实例实际上是一个代理,因此没有声明目标类的私有成员字段。我不希望为依赖项创建一个公共setter,因为我将修改我的接口纯粹是为了测试 我遵循了Spring社区给出的一些建议,但没有创建模拟,自动布线失败: <bean

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

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

我遵循了Spring社区给出的一些建议,但没有创建模拟,自动布线失败:

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

如果我将
构造函数arg
值设置为无效值,则在启动应用程序上下文时不会发生错误。

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

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

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

是创建一个返回mock的
FactoryBean
实现。
FactoryBean
接口允许Spring查询由FactoryBean创建的对象的类型

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

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

我可以使用Mockito执行以下操作:

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

最好的方法是:

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

更新
在上下文文件中,此模拟必须在声明任何依赖于它的自动连接字段之前列出。

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

@RunWith
现在才使用
SpringJUnit4Runner
进行集成测试


我将注意到,它似乎不能像Spring那样注入
List
。它只查找满足
列表
的模拟对象,不会注入模拟对象列表。我的解决方法是对手动实例化的列表使用
@Spy
,然后手动将模拟对象添加到该列表中进行单元测试。也许这是故意的,因为它确实迫使我密切关注一起被嘲笑的内容。

今天我发现我在Mockito bean之前声明a的spring上下文未能加载。 在模拟后移动后,应用程序上下文已成功加载。
注意:)

如果您使用的是spring>=3.0,请尝试使用Springs
@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框架参考:

下面的代码可用于自动布线-它不是最短的版本,但在仅适用于标准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> 

com.package.Dao

这将向测试类中注入任何模拟对象。在这种情况下,它将mockedObject注入testObject。上面提到了这一点,但下面是代码。

我找到了与teabot类似的答案,创建了一个提供模拟的MockFactory。我使用以下示例创建模拟工厂(因为到narkisr的链接已失效):



这也有助于防止Spring想要解决来自模拟bean的注入问题。

我有一个使用Spring Java Config和Mockito的非常简单的解决方案:

@Configuration
public class TestConfig {

    @Mock BeanA beanA;
    @Mock BeanB beanB;

    public TestConfig() {
        MockitoAnnotations.initMocks(this); //This is a key
    }

    //You basically generate getters and add @Bean annotation everywhere
    @Bean
    public BeanA getBeanA() {
        return beanA;
    }

    @Bean
    public BeanB getBeanB() {
        return beanB;
    }
}

根据上述方法发布一些示例

带弹簧:

@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;
}


如果在XML文件中首先/早期声明,则此^。Mockito 1.9.0/Spring 3.0.5

记录在案,我所有的测试都是通过初始化夹具来正确工作的,例如:

<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的“一种方式”。

更新-这里有一个新的答案:。这个答案只适用于3.2之前的Spring版本

我已经寻找了一段时间,寻找一个更明确的解决方案。这篇博文似乎涵盖了我的所有需求,并不依赖于bean声明的排序。这一切都归功于马蒂亚斯·塞弗森

基本上,实现一个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;
    }
}
package com.jayway.springmock;
导入org.mockito.mockito;
导入org.springframework.beans.factory.FactoryBean;
/**
*用于基于Mockito创建模拟bean的{@link FactoryBean},以便
*可以{@link@Autowired}到Spring测试配置中。
*
*@作者Mattias Severson,Jayway
*
*@见FactoryBean
*@见org.mockito.mockito
*/
公共类MockitoFactoryBean实现FactoryBean{
要删除的私有类;
/**
*创建所提供类的Mockito mock实例。
*@param classto删除要模拟的类。
*/
公共MockitoFactoryBean(类classToBeMocked){
this.classToBeMocked=classToBeMocked;
}
@凌驾
public T getObject()引发异常{
还它
@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService = new TestServiceImpl();
    @Mock
    private TestService2 testService2;
}
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>
<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>
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;
    }
}
<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>
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}
@ContextConfiguration(classes = { MvcConfiguration.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
    @Autowired
    private MyService myService;

    private MyDAO myDAOMock;

    @Before
    public void before() {
        myDAOMock = Mockito.mock(MyDAO.class);
        ReflectionTestUtils.setField(myService, "myDAO", myDAOMock);
    }

    // etc
}
@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
        }
    }
}
@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();
    }

}
@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");
    }

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

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}
@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() {
        ....
    }
}
@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() {
        ....
    }
}
@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();
    }
}