我想将一个Mockito模拟对象注入一个Spring(3+)bean中,以便使用JUnit进行单元测试。我的bean依赖项目前通过在私有成员字段上使用@Autowired
注释来注入。
我考虑过使用ReflectionTestUtils.setField
,但我希望注入的bean实例实际上是一个代理,因此不会声明目标类的私有成员字段。我不希望为依赖创建一个公共setter,因为我将修改我的界面纯粹是为了测试。
我已经关注了Spring社区提供的一些advice,但是没有创建模拟并且自动连接失败:
<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
值设置为无效,则在启动应用程序上下文时不会发生错误。
答案 0 :(得分:124)
最好的方法是:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
<强>更新强>
在上下文文件中,必须在任何自动装配字段之前列出此模拟,具体取决于它的声明。
答案 1 :(得分:106)
@InjectMocks
private MyTestObject testObject;
@Mock
private MyDependentObject mockedObject;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
这会将任何模拟对象注入测试类。在这种情况下,它会将mockedObject注入testObject。这是上面提到的,但这是代码。
答案 2 :(得分:62)
我使用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;
}
}
答案 3 :(得分:41)
假设:
@Service
public class MyService {
@Autowired
private MyDAO myDAO;
// etc
}
您可以通过自动装配加载正在测试的类,使用Mockito模拟依赖关系,然后使用Spring的ReflectionTestUtils将模拟注入正在测试的类中。
@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修正。
对于早期版本,解决方案是打开代理,如下所述:Transactional annotation avoids services being mocked(现在默认为ReflectionTestUtils.setField
)
答案 4 :(得分:34)
如果你正在使用Spring Boot 1.4,它有一个很棒的方法来做到这一点。只需在您的类上使用新品牌@SpringBootTest
,在字段上使用@MockBean
,Spring Boot将创建此类型的模拟,并将其注入上下文(而不是注入原始类型):
@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() {
....
}
}
如果您不想使用模拟但真正的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() {
....
}
}
答案 5 :(得分:19)
由于1.8.3 Mockito有@InjectMocks - 这非常有用。我的JUnit测试是@RunWith MockitoJUnitRunner,我构建的@Mock对象满足所测试类的所有依赖关系,这些对象都是在使用@InjectMocks注释私有成员时注入的。
我@Run仅使用SpringJUnit4Runner进行集成测试。
我会注意到它似乎无法以与Spring相同的方式注入List。它只查找满足List的Mock对象,并且不会注入Mock对象列表。我的解决方法是对手动实例化的列表使用@Spy,并手动。将模拟对象添加到该列表以进行单元测试。也许那是故意的,因为它当然迫使我密切关注一起被嘲弄的事情。
答案 6 :(得分:13)
更新:现在有更好,更清洁的解决方案来解决这个问题。请先考虑其他答案。
我最终在他的博客上找到了ronen对此的回答。我遇到的问题是由于方法Mockito.mock(Class c)
声明返回类型为Object
。因此Spring无法从工厂方法返回类型推断bean类型。
Ronen's solution是创建一个返回模拟的FactoryBean
实现。 FactoryBean
接口允许Spring查询工厂bean创建的对象类型。
我的模拟bean定义现在看起来像:
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
<property name="type" value="com.package.Dao" />
</bean>
答案 7 :(得分:11)
从Spring 3.2开始,这不再是一个问题。 Spring现在支持自动装配通用工厂方法的结果。请参阅此博客文章中标题为“通用工厂方法”的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/。
关键点是:
在Spring 3.2中,现在是工厂方法的泛型返回类型 正确推断,对于模拟的类型自动装配应该起作用 预期。因此,定制解决方案如a MockitoFactoryBean,EasyMockFactoryBean或Springockito可能没有 更长的必要。
这意味着这应该是开箱即用的:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
答案 8 :(得分:9)
下面的代码适用于自动装配 - 它不是最短的版本,但只适用于标准的spring / mockito罐子时才有用。
<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>
答案 9 :(得分:9)
如果您使用 spring&gt; = 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-framework-reference:Java-based container configuration
答案 10 :(得分:7)
我可以使用Mockito执行以下操作:
<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.abcd.StateMachine"/>
</bean>
答案 11 :(得分:7)
也许不是完美的解决方案,但我倾向于不使用弹簧来进行单元测试。单个bean(被测试的类)的依赖关系通常不会过于复杂,所以我只是直接在测试代码中进行注入。
答案 12 :(得分:6)
根据上述方法发布一些例子
使用Spring:
@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;
}
答案 13 :(得分:2)
更新 - 新答案: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>
答案 14 :(得分:2)
看看Springockito pace of development和number of open issues,我现在有点担心将它引入我的测试套件堆栈。最后一个版本在Spring 4发布之前完成的事实提出了像#34;是否可以轻松地将它与Spring 4集成?&#34;。我不知道,因为我没有尝试过。如果我需要在集成测试中模拟Spring bean,我更喜欢纯Spring方法。
可以选择使用简单的Spring功能来伪造Spring bean。您需要使用@Primary
,@Profile
和@ActiveProfiles
注释。 I wrote a blog post on the topic.
答案 15 :(得分:1)
我建议将您的项目迁移到Spring Boot 1.4。之后,您可以使用新的注释@MockBean
伪造您的com.package.Dao
答案 16 :(得分:1)
我根据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();
}
}
我写了post来解释它。
答案 17 :(得分:1)
<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
答案 18 :(得分:1)
我使用了Markus T回答的方法和ImportBeanDefinitionRegistrar
的简单帮助器实现的组合,它寻找自定义注释(@MockedBeans
),其中可以指定哪些类是嘲笑。我相信这种方法会导致一个简洁的单元测试,其中包含一些与模拟相关的样板代码。
以下是样本单元测试如何使用该方法:
@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
注释定义需要使用@Import(CustomImportBeanDefinitionRegistrar.class)
进行注释,而ImportBeanDefinitionRgistrar
需要将模拟bean定义添加到其registerBeanDefinitions
方法中的配置中。
如果您喜欢这种方法,可以在implementations上找到示例blogpost。
答案 19 :(得分:1)
我找到了一个与teabot类似的答案来创建一个提供模拟的MockFactory。我使用以下示例来创建模拟工厂(因为到narkisr的链接已经死了): http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testUtils/MocksFactory.java
<bean id="someFacade" class="nl.package.test.MockFactory">
<property name="type" value="nl.package.someFacade"/>
</bean>
这也有助于防止Spring想要解决来自模拟bean的注入。
答案 20 :(得分:0)
为了记录,我的所有测试都只是通过使夹具延迟初始化来正确工作,例如:
<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解释here(在帖子的底部),一个变通方法正在改变声明bean的顺序 - 懒惰的初始化是“有点”,其中夹具被声明为结束。
答案 21 :(得分:0)
今天我发现我在Mockito bean之前声明的春天环境无法加载。 在移动模拟后,应用程序上下文已成功加载。 小心:)
答案 22 :(得分:0)
如果您使用的是 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 Context 重新创建导致构建测试阶段缓慢(请参阅 Using @MockBean in tests forces reloading of Application Context 或 the problem with @MockBean)
答案 23 :(得分:-1)
如果您使用控制器注入,请确保您的局部变量不是“最终”