我想将一个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
值设置为无效,则在启动应用程序上下文时不会发生错误 .
23 回答
看看Springockito pace of development和number of open issues,我现在有点担心将它介绍到我的测试套件堆栈中 . 最后一个版本在Spring 4发布之前完成的事实提出了像"Is it possible to easily integrate it with Spring 4?"这样的问题 . 我不试试 . 如果我需要在集成测试中模拟Spring bean,我更喜欢纯Spring方法 .
有一个选项可以使用简单的Spring功能来伪造Spring bean . 您需要使用
@Primary
,@Profile
和@ActiveProfiles
注释 . I wrote a blog post on the topic.这会将任何模拟对象注入测试类 . 在这种情况下,它会将mockedObject注入testObject . 这是上面提到的,但这里是代码 .
如果在XML文件中首先/早期声明,这^非常有效 . Mockito 1.9.0 / Spring 3.0.5
今天我发现我在Mockito beans 之前声明的 Spring 天环境未能加载 . 在移动模拟后,应用程序上下文已成功加载 . 照顾自己 :)
下面的代码适用于自动装配 - 它不是最短的版本,但它只适用于标准的spring / mockito jar时很有用 .
Update: 现在有更好,更清洁的解决方案来解决这个问题 . 请先考虑其他答案 .
我最终在他的博客上找到了ronen的回答 . 我遇到的问题是由于方法
Mockito.mock(Class c)
声明返回类型Object
. 因此Spring无法从工厂方法返回类型推断bean类型 .Ronen's solution是创建一个返回模拟的
FactoryBean
实现 .FactoryBean
接口允许Spring查询工厂bean创建的对象类型 .我的模拟bean定义现在看起来像:
鉴于:
您可以通过自动装配加载正在测试的类,使用Mockito模拟依赖关系,然后使用Spring的ReflectionTestUtils将模拟注入正在测试的类中 .
请注意,在Spring 4.3.1之前,此方法不适用于代理后面的服务(例如,使用
@Transactional
或Cacheable
注释) . 这已由SPR-14050修复 .对于早期版本,解决方案是打开代理,如下所述:Transactional annotation avoids services being mocked(默认情况下
ReflectionTestUtils.setField
默认情况下)从Spring 3.2开始,这不再是一个问题 . Spring现在支持自动装配通用工厂方法的结果 . 请参阅此博客文章中名为"Generic Factory Methods"的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ .
关键点是:
这意味着这应该是开箱即用的:
最好的方法是:
Update
在上下文文件中,必须在任何自动装配字段之前列出此模拟,具体取决于它的声明 .
由于1.8.3 Mockito有@InjectMocks - 这非常有用 . 我的JUnit测试是@RunWith MockitoJUnitRunner,我构建的@Mock对象满足所测试类的所有依赖关系,这些对象都是在私有成员使用@InjectMocks注释时注入的 .
我@Run仅使用SpringJUnit4Runner进行集成测试 .
我会注意到它似乎无法以与Spring相同的方式注入List . 它只查找满足List的Mock对象,并且不会注入Mock对象列表 . 我的解决方法是对手动实例化的列表使用@Spy,并手动将模拟对象添加到该列表以进行单元测试 . 也许这是故意的,因为它肯定迫使我密切关注被 Mock 的事情 .
我使用了Markus T回答的方法和
ImportBeanDefinitionRegistrar
的简单帮助器实现的组合,该实现查找自定义注释(@MockedBeans
),其中可以指定要模拟哪些类 . 我相信这种方法可以实现简洁的单元测试一些与mocking相关的样板代码被删除了 .以下是采用该方法的样本单元测试的外观:
要实现这一点,您需要定义两个简单的辅助类 - 自定义注释(
@MockedBeans
)和自定义ImportBeanDefinitionRegistrar
实现 .@MockedBeans
注释定义需要使用@Import(CustomImportBeanDefinitionRegistrar.class)
注释,ImportBeanDefinitionRgistrar
需要将模拟bean定义添加到其registerBeanDefinitions
方法中的配置中 .如果您喜欢这种方法,可以在我的blogpost上找到样本implementations .
我有一个使用Spring Java Config和Mockito的非常简单的解决方案:
我根据Kresimir Nesek的提议开发了一个解决方案 . 我添加了一个新的注释@EnableMockedBean,以使代码更清晰和模块化 .
我写了一封post解释它 .
为了记录,我的所有测试只需使夹具延迟初始化即可正常工作,例如:
我认为理由是Mattias解释here(在帖子的底部),一个解决方法是改变bean声明的顺序 - 懒惰的初始化是"sort of",最后声明了夹具 .
也许不是完美的解决方案,但我倾向于不使用 spring 来进行单元测试 . 单个bean(被测试的类)的依赖关系通常不会过于复杂,所以我只是直接在测试代码中进行注入 .
我找到了一个与teabot类似的答案来创建一个提供模拟的MockFactory . 我使用以下示例来创建模拟工厂(因为到narkisr的链接已经死了):http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testUtils/MocksFactory.java
这也有助于防止Spring想要解决来自模拟bean的注入 .
如果你使用的是Spring Boot 1.4,它有一个很棒的方法 . 只需在你的类上使用新品牌
@SpringBootTest
,在字段上使用@MockBean
,Spring Boot将创建一个这种类型的模拟器,它会将它注入上下文(而不是注入原始的):另一方面,如果您没有使用Spring Boot,或者您使用的是以前的版本,那么您将需要做更多的工作:
创建一个
@Configuration
bean,将您的模拟注入Spring上下文:使用
@Primary
注释,如果没有指定限定符,则告诉spring该bean具有优先级 .确保使用
@Profile("useMocks")
注释该类,以便控制哪些类将使用模拟以及哪些类将使用真实bean .最后,在您的测试中,激活
userMocks
Profiles :如果您没有't want to use the mock but the real bean, just don' t激活
useMocks
Profiles :基于上述方法发布一些示例
随着 Spring 天:
没有 Spring 天:
如果您正在使用 spring >= 3.0 ,请尝试使用Springs
@Configuration
注释来定义应用程序上下文的一部分如果你不想使用@ImportResource,它也可以反过来做:
有关更多信息,请查看spring-framework-reference:Java-based container configuration
我可以使用Mockito执行以下操作:
我建议将您的项目迁移到Spring Boot 1.4 . 之后你可以使用新的注释@MockBean伪造你的
com.package.Dao
如果使用Controller Injection,请确保您的局部变量不是“最终”
Update - 这里的新答案:https://stackoverflow.com/a/19454282/411229 . 这个答案仅适用于3.2之前的Spring版本 .
我've looked for a while for a more definitive solution to this. This blog post seems to cover all my needs and doesn'依赖于bean声明的排序 . 所有归功于Mattias Severson . http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
基本上,实现FactoryBean
接下来用以下内容更新spring配置: