将Mockito嘲笑注入Spring bean
我想将一个Mockito模拟对象注入到Spring(3+)bean中,以便与JUnit进行unit testing。 我的bean依赖关系目前是通过在私有成员字段上使用@Autowired
注解来注入的。
我已经考虑使用ReflectionTestUtils.setField
但是我希望注入的bean实例实际上是一个代理,因此不声明目标类的私有成员字段。 我不想为依赖项创build公共setter,因为我将纯粹为了testing目的而修改我的接口。
我遵循了Spring社区给出的一些build议 ,但是模拟并没有被创build,而且自动布线失败:
<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
值设置为无效,那么启动应用程序上下文时不会发生错误。
最好的方法是:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean>
更新
在上下文文件中,这个模拟必须在声明任何自动assembly的字段之前被列出。
@InjectMocks private MyTestObject testObject @Mock private MyDependentObject mockedObject @Before public void setup() { MockitoAnnotations.initMocks(this); }
这将在testing类中注入任何模拟对象,因此在这种情况下,它将在testObject中注入mockedObject。 这是上面提到的,但这里是代码。
我有一个使用Spring Java Config和Mockito的非常简单的解决scheme:
@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; } }
鉴于:
@Service public class MyService { @Autowired private MyDAO myDAO; // etc }
你可以通过自动assembly加载正在testing的类,用Mockito模拟依赖,然后使用Spring的ReflectionTestUtils将模拟注入被testing的类中。
@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 }
请注意,在Spring 4.3.1之前,这个方法不能用代理之后的服务(例如用@Transactional
或Cacheable
注释)。 这已由SPR-14050修复。
对于较早的版本,解决scheme是展开代理,如下所述: https : //stackoverflow.com/a/17548683/5138796 (这是默认情况下, ReflectionTestUtils.setField
执行的操作)
如果你使用的是Spring Boot 1.4,它有一个很好的方法来做到这一点。 只需在你的类和@MockBean
上使用新品牌@SpringBootTest
@MockBean
,Spring Boot将创build这种types的模拟,并将其注入到上下文中(而不是注入原始模型):
@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或者你使用的是以前的版本,那么你需要做更多的工作:
创build一个@Configuration
bean,将你的模拟注入到Spring上下文中:
@Configuration @Profile("useMocks") public class MockConfigurer { @Bean @Primary public MyBean myBeanSpy() { return mock(MyBean.class); } }
使用@Primary
注解,你告诉spring,如果没有指定限定符,这个bean有优先权。
确保使用@Profile("useMocks")
对类进行注释,以便控制哪些类将使用模拟,哪些将使用实际的bean。
最后,在你的testing中,激活userMocks
configuration文件:
@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() { .... } }
如果你不想使用模拟,但真正的豆,只是不要激活useMocks
configuration文件:
@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 – 这是非常有用的。 我的JUnittesting是@RunWith MockitoJUnitRunner和我build立@Mock对象,满足所有的testing类的依赖关系,当私人成员用@InjectMocks注释时,这些都被注入。
我@RunWith现在只用于集成testing的SpringJUnit4Runner。
我会注意到它似乎不能像Spring一样注入List。 它只能看到满足List的Mock对象,而不会注入Mock对象的列表。 对我来说,解决方法是对手动实例化列表使用@Spy,并手动将模拟对象添加到该列表以进行unit testing。 也许这是故意的,因为这肯定会迫使我密切关注被一起嘲笑的东西。
更新:现在有更好,更清洁的解决scheme来解决这个问题。 请先考虑其他答案。
我最终通过博客find了答案。 我遇到的问题是由于方法Mockito.mock(Class c)
声明返回types的Object
。 因此,Spring无法从工厂方法返回types推断出bean的types。
Ronen的解决scheme是创build一个返回mocks的FactoryBean
实现。 FactoryBean
接口允许Spring查询由工厂bean创build的对象的types。
我的模拟bean定义如下所示:
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean>
从3.2版开始,这不再是一个问题。 Spring现在支持通用工厂方法的结果自动assembly。 请参阅本博客文章中的“通用工厂方法”一节: http : //spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ 。
关键是:
在Spring 3.2中,工厂方法的通用返回types现在可以被正确推断,并且按照types为自动assembly应该按照预期工作。 因此,自定义的解决scheme(如MockitoFactoryBean,EasyMockFactoryBean或Springockito)可能不再需要。
这意味着这应该开箱即用:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean>
下面的代码适用于自动assembly – 这不是最短的版本,但它只适用于标准的弹簧/ mockitojar子时才有用。
<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>
如果使用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>
我可以使用Mockito做以下事情:
<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.abcd.StateMachine"/> </bean>
也许不是完美的解决scheme,但我倾向于不使用弹簧做unit testing的DI。 单个bean(被testing的类)的依赖性通常不会太复杂,所以我只是直接在testing代码中进行注入。
根据上述方法发表几个例子
spring:
@ContextConfiguration(locations = { "classpath:context.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class TestServiceTest { @InjectMocks private TestService testService; @Mock private TestService2 testService2; }
没有spring:
@RunWith(MockitoJUnitRunner.class) public class TestServiceTest { @InjectMocks private TestService testService = new TestServiceImpl(); @Mock private TestService2 testService2; }
更新 – 新的答案在这里: https : //stackoverflow.com/a/19454282/411229 。 这个答案只适用于3.2之前的Spring版本。
我已经寻找了一个更确定的解决scheme这一点。 这篇博文似乎涵盖了我所有的需求,并不依赖于bean声明的sorting。 所有信贷给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; } }
接下来用下面的方法更新你的springconfiguration:
<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>
看看Springockito的发展速度和开放问题的数量 ,我现在有点担心将它引入到我的testing套件栈中。 事实上,在Spring 4发布之前发布的最后一个版本提出了“是否可以轻松地将其与Spring 4集成?”这样的问题。 我不知道,因为我没有尝试。 如果我需要在集成testing中模拟Spring bean,我更喜欢纯粹的Spring方法。
有一个选项可以用Spring的简单function来伪造Spring bean。 你需要使用@Primary
, @ActiveProfiles
和@ActiveProfiles
注解。 我写了关于这个话题的博客文章。
我发现了一个类似的答案作为茶壶创build一个MockFactory提供了模拟。 我用下面的例子来创build模拟工厂(因为narkisr的链接已经死了): http ://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/ 组织/ randompage /书签/后端/ testUtils / MocksFactory.java
<bean id="someFacade" class="nl.package.test.MockFactory"> <property name="type" value="nl.package.someFacade"/> </bean>
这也有助于防止Spring想要解决来自嘲弄的bean的注入。
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean>
这个^工作得很好,如果在XML文件中首先声明的话。 Mockito 1.9.0 / Spring 3.0.5
我使用了Markus T回答中使用的方法和ImportBeanDefinitionRegistrar
一个简单的帮助实现,它们查找一个自定义注释( @MockedBeans
),其中可以指定哪些类将被模拟。 我相信这种方法会导致一些简单的unit testing,其中一些与嘲笑相关的样板代码被删除。
下面是一个样本unit testing的方法:
@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
注解定义需要使用ImportBeanDefinitionRgistrar
@Import(CustomImportBeanDefinitionRegistrar.class)
进行注释, ImportBeanDefinitionRgistrar
需要将mocked bean定义添加到其registerBeanDefinitions
方法中的configuration中。
如果你喜欢这个方法,你可以在我的blogpost上find样本实现 。
我根据Kresimir Nesek的build议开发了一个解决scheme。 我添加了一个新的注解@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(); } }
我写了一篇文章解释它。
我会build议把你的项目迁移到Spring Boot 1.4。 之后,你可以使用新的注释@MockBean
来伪造你的com.package.Dao
今天我发现,在Mockito豆之前,我宣布了一个spring的背景,没有加载。 移动AFTER后,应用程序上下文被成功加载。 保重 :)
为了logging,所有我的testing正确地工作,只是使灯具懒惰初始化,例如:
<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的“sorting”。
如果您使用控制器注入,请确保您的本地variables不是“最终”