Injecting Mockito mocks into a Spring bean
为了使用JUnit进行单元测试,我想将mock i to模拟对象注入到Spring(3+)bean中。我的bean依赖项目前是通过在私有成员字段上使用
我考虑过使用
我遵循了Spring社区提供的一些建议,但模拟模型没有创建,自动布线失败:
1 2 3 | <bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> |
我目前遇到的错误如下:
1 2 3 4 5 6 7 8 9 10 | ... 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) |
如果我将
最好的方法是:
1 2 3 | <bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> |
更新< BR/>在上下文文件中,此模拟必须在声明依赖于它的任何自动连线字段之前列出。
1 2 3 4 5 6 7 8 9 10 | @InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; @Before public void setup() { MockitoAnnotations.initMocks(this); } |
这将向测试类中注入任何模拟对象。在这种情况下,它将把mockedObject注入到testObject中。这是上面提到的,但这里是代码。
我有一个非常简单的解决方案,使用Spring Java CONFIG和Mockito:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @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; } } |
鉴于:
1 2 3 4 5 6 7 | @Service public class MyService { @Autowired private MyDAO myDAO; // etc } |
您可以让正在测试的类通过自动布线加载,用mockito模拟依赖关系,然后使用Spring的ReflectionTestUtils将mock注入正在测试的类中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @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 } |
请注意,在Spring4.3.1之前,此方法不适用于代理背后的服务(例如,用
对于早期版本,解决方案是解包代理,如前所述:事务性注释避免服务被模拟(这是
如果您使用的是SpringBoot1.4,那么它有一种很棒的方法。只需在课堂上使用新品牌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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或使用的是以前的版本,则需要做更多的工作:
创建一个
1 2 3 4 5 6 7 8 9 10 | @Configuration @Profile("useMocks") public class MockConfigurer { @Bean @Primary public MyBean myBeanSpy() { return mock(MyBean.class); } } |
使用
确保用
最后,在测试中,激活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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() { .... } } |
如果您不想使用模拟而使用真正的bean,只需不激活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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() { .... } } |
因为1.8.3 Mockito有@injectmocks-这是非常有用的。我的JUnit测试是@runwith the mockitojunitrunner,我构建了@mock对象,它满足被测试类的所有依赖项,当私有成员用@injectmocks注释时,这些依赖项都被注入。
i@runwith the springjunit4runner for integration tests only now.
我会注意到,它似乎不能像Spring那样以同样的方式注入列表。它只查找满足列表的模拟对象,而不会插入模拟对象列表。对于我来说,解决方法是对手动实例化的列表使用@spy,然后手动将模拟对象添加到该列表中进行单元测试。也许这是故意的,因为它确实迫使我密切关注被嘲笑的东西。
更新:现在有更好、更清洁的解决方案来解决这个问题。请先考虑其他答案。
我最终在罗南的博客上找到了答案。我遇到的问题是由于方法
Ronen的解决方案是创建一个返回mock的
我的模拟bean定义现在看起来像:
1 2 3 | <bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean> |
从Spring3.2开始,这不再是一个问题。现在Spring支持通用工厂方法结果的自动连接。请参阅本博客中标题为"通用工厂方法"的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/。
关键是:
In Spring 3.2, generic return types for factory methods are now
properly inferred, and autowiring by type for mocks should work as
expected. As a result, custom work-arounds such as a
MockitoFactoryBean, EasyMockFactoryBean, or Springockito are likely no
longer necessary.
这意味着这应该是开箱即用的:
1 2 3 | <bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> |
如果使用的是spring>=3.0,请尝试使用spring
1 2 3 4 5 6 7 8 9 10 | @Configuration @ImportResource("com/blah/blurk/rest-of-config.xml") public class DaoTestConfiguration { @Bean public ApplicationService applicationService() { return mock(ApplicationService.class); } } |
如果您不想使用@importresource,也可以用另一种方法:
1 2 3 4 5 6 7 | <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框架参考:基于Java的容器配置
下面的代码适用于自动布线——它不是最短的版本,但在只适用于标准的Spring/Mockito JAR时非常有用。
1 2 3 4 | <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> |
我可以使用mockito执行以下操作:
1 2 3 | <bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.abcd.StateMachine"/> </bean> |
也许不是完美的解决方案,但我倾向于不使用Spring为单元测试做DI。单个bean(测试中的类)的依赖关系通常并不太复杂,所以我只是直接在测试代码中进行注入。
基于上述方法发布几个示例
带弹簧:
1 2 3 4 5 6 7 8 | @ContextConfiguration(locations = {"classpath:context.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class TestServiceTest { @InjectMocks private TestService testService; @Mock private TestService2 testService2; } |
没有春天:
1 2 3 4 5 6 7 | @RunWith(MockitoJUnitRunner.class) public class TestServiceTest { @InjectMocks private TestService testService = new TestServiceImpl(); @Mock private TestService2 testService2; } |
更新-新答案:https://stackoverflow.com/a/19454282/411229。此答案仅适用于3.2之前的弹簧版本。
我已经找了一段时间来寻找更明确的解决办法。这篇博文似乎涵盖了我所有的需求,并不依赖于bean声明的排序。全部归功于马蒂亚斯·塞弗森。http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
基本上,实现FactoryBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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配置:
1 2 3 4 5 6 7 | <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> |
考虑到SpringLockito的开发速度和开放问题的数量,我现在有点担心将它引入我的测试套件堆栈。上一次发布是在Spring4发布之前完成的,这一事实带来了这样的问题:"是否可以轻松地将其与Spring4集成?"我不知道,因为我没试过。如果需要在集成测试中模拟SpringBean,我更喜欢纯Spring方法。
有一个选项可以用简单的弹簧特性来伪造SpringBean。您需要使用
我找到了与Teabot相似的答案,创建了一个提供模拟的MockFactory。我使用下面的示例创建了模拟工厂(因为到narkisr的链接已断开):http://hg.randompage.org/java/src/407e78aa08a00/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testuils/mocksfactory.java
1 2 3 | <bean id="someFacade" class="nl.package.test.MockFactory"> <property name="type" value="nl.package.someFacade"/> </bean> |
这也有助于防止Spring想要解决来自模拟bean的注入问题。
我结合了MarkusT在应答中使用的方法和一个简单的
下面是使用这种方法的示例单元测试的外观:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @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 } } } |
要做到这一点,您需要定义两个简单的助手类-自定义注释(
如果您喜欢这种方法,可以在我的blogpost上找到示例实现。
我根据Kresimir Nesek的建议开发了一个解决方案。我添加了一个新的注释@EnableMockedBean,以便使代码更清晰和模块化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @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(); } } |
我写了一篇文章解释它。
我建议将您的项目迁移到SpringBoot1.4。之后,您可以使用新的注释
1 2 3 | <bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean> |
如果在XML文件的第一个/早期声明,则此^非常有效。Mockito 1.9.0/弹簧3.0.5
对于记录,我的所有测试都是通过使fixture lazy初始化来正确工作的,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <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 beans之前声明的一个Spring上下文加载失败。移动完模拟后,应用程序上下文被成功加载。小心:
如果使用控制器注入,请确保局部变量不是"最终"变量。