将 Mockito 模拟注入 Spring bean
我想将 Mockito 模拟对象注入到 Spring (3+) bean 中,以便使用 JUnit 进行单元测试。我的 bean 依赖项当前是通过在私有成员字段上使用 @Autowired
注释来注入的。
我考虑过使用ReflectionTestUtils.setField,但我希望注入的bean实例实际上是一个代理,因此没有声明目标类的私有成员字段。我不希望为依赖项创建公共设置器,因为然后我将纯粹出于测试目的修改我的界面。
我遵循了 Spring 社区给出的一些建议,但是模拟未创建且自动装配失败:
<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
值设置为无效的值,则在启动应用程序上下文时不会发生错误。
I would like to inject a Mockito mock object into a Spring (3+) bean for the purposes of unit testing with JUnit. My bean dependencies are currently injected by using the @Autowired
annotation on private member fields.
I have considered using ReflectionTestUtils.setField
but the bean instance that I wish to inject is actually a proxy and hence does not declare the private member fields of the target class. I do not wish to create a public setter to the dependency as I will then be modifying my interface purely for the purposes of testing.
I have followed some advice given by the Spring community but the mock does not get created and the auto-wiring fails:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
The error I currently encounter is as follows:
...
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)
If I set the constructor-arg
value to something invalid no error occurs when starting the application context.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(25)
最好的方法是:
更新
在上下文文件中,此模拟必须在声明依赖于它的任何自动装配字段之前列出。
The best way is:
Update
In the context file this mock must be listed before any autowired field depending on it is declared.
这会将所有模拟对象注入到测试类中。在这种情况下,它将把mockedObject注入到testObject中。上面提到了这一点,但这是代码。
This will inject any mocked objects into the test class. In this case it will inject mockedObject into the testObject. This was mentioned above but here is the code.
我有一个使用 Spring Java Config 和 Mockito 的非常简单的解决方案:
I have a very simple solution using Spring Java Config and Mockito:
给定:
您可以通过自动装配加载正在测试的类,使用 Mockito 模拟依赖关系,然后使用 Spring 的 ReflectionTestUtils 将模拟注入到正在测试的类中。
请注意,在 Spring 4.3.1 之前,此方法不适用于代理后面的服务(例如,使用
@Transactional
或Cacheable
注释)。 SPR-14050 已修复此问题。对于早期版本,解决方案是解开代理,如下所述: 事务注释避免服务被模拟(这是
ReflectionTestUtils.setField
现在默认执行的操作)Given:
You can have the class that is being tested loaded via autowiring, mock the dependency with Mockito, and then use Spring's ReflectionTestUtils to inject the mock into the class being tested.
Please note that before Spring 4.3.1, this method won't work with services behind a proxy (annotated with
@Transactional
, orCacheable
, for example). This has been fixed by SPR-14050.For earlier versions, a solution is to unwrap the proxy, as described there: Transactional annotation avoids services being mocked (which is what
ReflectionTestUtils.setField
does by default now)如果您使用 Spring Boot 1.4,它有一个很棒的方法来做到这一点。只需在类上使用新品牌
@SpringBootTest
并在字段上使用@MockBean
,Spring Boot 将创建这种类型的模拟,并将其注入到上下文中(而不是注入原始版本):另一方面,如果您不使用 Spring Boot 或者使用以前的版本,则需要做更多工作:
创建一个
@Configuration
bean将您的模拟注入到 Spring 上下文中:使用 @Primary 注释,您告诉 spring 如果未指定限定符,则该 bean 具有优先级。
确保使用 @Profile("useMocks") 注释该类,以控制哪些类将使用模拟,哪些类将使用真实的 bean。
最后,在您的测试中,激活
userMocks
配置文件:如果您不想使用模拟而使用真实的 bean,则不要激活
useMocks
配置文件:If you're using Spring Boot 1.4, it has an awesome way of doing this. Just use new brand
@SpringBootTest
on your class and@MockBean
on the field and Spring Boot will create a mock of this type and it will inject it into the context (instead of injecting the original one):On the other hand, if you're not using Spring Boot or are you using a previous version, you'll have to do a bit more work:
Create a
@Configuration
bean that injects your mocks into Spring context:Using
@Primary
annotation you're telling spring that this bean has priority if no qualifier are specified.Make sure you annotate the class with
@Profile("useMocks")
in order to control which classes will use the mock and which ones will use the real bean.Finally, in your test, activate
userMocks
profile:If you don't want to use the mock but the real bean, just don't activate
useMocks
profile:自 1.8.3 起,Mockito 具有
@ InjectMocks
- 这非常有用。我的 JUnit 测试是@RunWith
和MockitoJUnitRunner
,我构建了@Mock
对象来满足被测试类的所有依赖项,这些依赖项都是注入的当私有成员用@InjectMocks
注释时。我现在仅将
@RunWith
SpringJUnit4Runner
用于集成测试。我会注意到它似乎无法以与 Spring 相同的方式注入
List
。它只查找满足List
的Mock对象,并且不会注入Mock对象列表。我的解决方法是对手动实例化的列表使用@Spy
,并手动将模拟对象添加到该列表中以进行单元测试。也许这是故意的,因为这确实迫使我密切关注一起被嘲笑的事情。Since 1.8.3 Mockito has
@InjectMocks
- this is incredibly useful. My JUnit tests are@RunWith
theMockitoJUnitRunner
and I build@Mock
objects that satisfy all the dependencies for the class being tested, which are all injected when the private member is annotated with@InjectMocks
.I
@RunWith
theSpringJUnit4Runner
for integration tests only now.I will note that it does not seem to be able to inject
List<T>
in the same manner as Spring. It looks only for a Mock object that satisfies theList
, and will not inject a list of Mock objects. The workaround for me was to use a@Spy
against a manually instantiated list, and manually .add the mock object(s) to that list for unit testing. Maybe that was intentional, because it certainly forced me to pay close attention to what was being mocked together.更新:现在有更好、更清晰的解决方案来解决这个问题。请先考虑其他答案。
我最终在 ronen 的博客上找到了这个问题的答案。我遇到的问题是由于方法
Mockito.mock(Class c)
声明了Object
的返回类型。因此 Spring 无法从工厂方法返回类型推断 bean 类型。Ronen 的解决方案 是创建一个
FactoryBean
返回模拟的实现。FactoryBean
接口允许 Spring 查询工厂 bean 创建的对象的类型。我的模拟 bean 定义现在看起来像:
Update: There are now better, cleaner solutions to this problem. Please consider the other answers first.
I eventually found an answer to this by ronen on his blog. The problem I was having is due to the method
Mockito.mock(Class c)
declaring a return type ofObject
. Consequently Spring is unable to infer the bean type from the factory method return type.Ronen's solution is to create a
FactoryBean
implementation that returns mocks. TheFactoryBean
interface allows Spring to query the type of objects created by the factory bean.My mocked bean definition now looks like:
从 Spring 3.2 开始,这不再是问题。 Spring 现在支持自动装配通用工厂方法的结果。请参阅此博客文章中标题为“通用工厂方法”的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/。
关键点是:
这意味着这应该是开箱即用的:
As of Spring 3.2, this is no longer an issue. Spring now supports Autowiring of the results of generic factory methods. See the section entitled "Generic Factory Methods" in this blog post: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/.
The key point is:
Which means this should work out of the box:
如果您使用的是 spring >= 3.0,请尝试使用 Springs
@Configuration
注解来定义部分应用程序上下文如果您不想使用 @ImportResource,也可以用相反的方式完成:
有关更多信息,请查看 spring-framework-reference : 基于Java的容器配置
If you're using spring >= 3.0, try using Springs
@Configuration
annotation to define part of the application contextIf you don't want to use the @ImportResource, it can be done the other way around too:
For more information, have a look at spring-framework-reference : Java-based container configuration
下面的代码适用于自动装配 - 它不是最短的版本,但当它只适用于标准 spring/mockito jar 时很有用。
Below code works with autowiring - it is not the shortest version but useful when it should work only with standard spring/mockito jars.
也许不是完美的解决方案,但我倾向于不使用 spring 进行单元测试的 DI。单个 bean(被测试的类)的依赖关系通常不会太复杂,所以我只是直接在测试代码中进行注入。
Perhaps not the perfect solution, but I tend not to use spring to do DI for unit tests. the dependencies for a single bean (the class under test) usually aren't overly complex so I just do the injection directly in the test code.
我可以使用 Mockito 执行以下操作:
I can do the following using Mockito:
基于上述方法发布一些示例
使用 Spring:
不使用 Spring:
Posting a few examples based on the above approaches
With Spring:
Without Spring:
更新 - 新答案:https://stackoverflow.com/a/19454282/411229 。这个答案只适用于 Spring 3.2 之前的版本。
我花了一段时间寻找更明确的解决方案。这篇博文似乎涵盖了我的所有需求,并且不依赖于 bean 声明的顺序。这一切都归功于马蒂亚斯·塞弗森。 http://www .jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
基本上,实现一个 FactoryBean
接下来使用以下内容更新您的 spring 配置:
Update - new answer here: https://stackoverflow.com/a/19454282/411229. This answer only applies to those on Spring versions before 3.2.
I've looked for a while for a more definitive solution to this. This blog post seems to cover all my needs and doesn't rely on ordering of bean declarations. All credit to Mattias Severson. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
Basically, implement a FactoryBean
Next update your spring config with the following:
我使用了 Markus T 回答中使用的方法和
ImportBeanDefinitionRegistrar
的简单帮助器实现的组合,该实现查找自定义注释 (@MockedBeans
),其中可以指定哪个类是要被嘲笑的。我相信这种方法会产生简洁的单元测试,并删除一些与模拟相关的样板代码。以下是使用该方法的示例单元测试的外观:
要实现这一点,您需要定义两个简单的帮助器类 - 自定义注释 (
@MockedBeans
) 和自定义注释ImportBeanDefinitionRegistrar
实现。@MockedBeans
注释定义需要使用@Import(CustomImportBeanDefinitionRegistrar.class)
进行注释,并且ImportBeanDefinitionRgistrar
需要将模拟 bean 定义添加到配置中它是registerBeanDefinitions
方法。如果您喜欢这种方法,可以找到示例 在我的 博客文章。
I use a combination of the approach used in answer by Markus T and a simple helper implementation of
ImportBeanDefinitionRegistrar
that looks for a custom annotation (@MockedBeans
) in which one can specify which classes are to be mocked. I believe that this approach results in a concise unit test with some of the boilerplate code related to mocking removed.Here's how a sample unit test looks with that approach:
To make this happen you need to define two simple helper classes - custom annotation (
@MockedBeans
) and a customImportBeanDefinitionRegistrar
implementation.@MockedBeans
annotation definition needs to be annotated with@Import(CustomImportBeanDefinitionRegistrar.class)
and theImportBeanDefinitionRgistrar
needs to add mocked beans definitions to the configuration in it'sregisterBeanDefinitions
method.If you like the approach you can find sample implementations on my blogpost.
看看 Springockito 的发展速度 和 未解决问题的数量,我现在有点担心将其引入我的测试套件堆栈中。事实上,最后一个版本是在 Spring 4 发布之前完成的,这带来了诸如“是否可以轻松地将其与 Spring 4 集成?”之类的问题。我不知道,因为我没有尝试过。如果我需要在集成测试中模拟 Spring bean,我更喜欢纯 Spring 方法。
可以选择仅使用简单的 Spring 功能来伪造 Spring bean。您需要为其使用
@Primary
、@Profile
和@ActiveProfiles
注解。 我写了一篇关于该主题的博客文章。Looking at Springockito pace of development and number of open issues, I would be little bit worried to introduce it into my test suite stack nowadays. Fact that last release was done before Spring 4 release brings up questions like "Is it possible to easily integrate it with Spring 4?". I don't know, because I didn't try it. I prefer pure Spring approach if I need to mock Spring bean in integration test.
There is an option to fake Spring bean with just plain Spring features. You need to use
@Primary
,@Profile
and@ActiveProfiles
annotations for it. I wrote a blog post on the topic.我找到了与 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 的注入。
I found a similar answer as teabot to create a MockFactory that provides the mocks. I used the following example to create the mock factory (since the link to narkisr are dead):
http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testUtils/MocksFactory.java
This also helps to prevent that Spring wants to resolve the injections from the mocked bean.
如果在 XML 文件中首先/早期声明,则 this ^ 效果非常好。 Mockito 1.9.0/Spring 3.0.5
this ^ works perfectly well if declared first/early in the XML file. Mockito 1.9.0/Spring 3.0.5
我根据 Kresimir Nesek 的建议开发了一个解决方案。我添加了一个新的注释 @EnableMockedBean 以使代码更加简洁和模块化。
我写了一篇帖子解释它。
I developed a solution based on the proposal of Kresimir Nesek. I added a new annotation @EnableMockedBean in order to make the code a bit cleaner and modular.
I have written a post explaining it.
我建议将您的项目迁移到 Spring Boot 1.4。之后,您可以使用新注释
@MockBean
来伪造你的com.package.Dao
I would suggest to migrate your project to Spring Boot 1.4. After that you can use new annotation
@MockBean
to fake yourcom.package.Dao
今天我发现我在 Mockito beans 之前声明了 a 的 spring 上下文无法加载。
移动到模拟之后,应用程序上下文已成功加载。
小心 :)
Today I found out that a spring context where I declared a before the Mockito beans, was failing to load.
After moving the AFTER the mocks, the app context was loaded successfully.
Take care :)
根据记录,我的所有测试都可以通过使夹具延迟初始化来正确工作,例如:
我认为基本原理是 Mattias 解释的 这里(在帖子的底部),解决方法是更改 bean 的声明顺序 - 懒惰初始化“有点”在最后声明了固定装置。
For the record, all my tests correctly work by just making the fixture lazy-initialized, e.g.:
I suppose the rationale is the one Mattias explains here (at the bottom of the post), that a workaround is changing the order the beans are declared - lazy initialization is "sort of" having the fixture declared at the end.
如果您使用的是 Spring Boot 2.2+,则可以使用 @MockInBean 作为替代方案到
@MockBean
并保持 Spring 上下文干净:免责声明:我创建这个库是为了避免 @MockBean/@SpringBean 导致 Spring 上下文重新创建,从而导致构建测试阶段缓慢(请参阅< a href="https://stackoverflow.com/questions/45587213/using-mockbean-in-tests-forces-reloading-of-application-context">在测试中使用 @MockBean 强制重新加载应用程序上下文 或@MockBean 的问题)
If you're using spring boot 2.2+, you can use @MockInBean as an alternative to
@MockBean
and keep your Spring context clean:disclaimer: I created this library to avoid Spring Context re-creation caused by @MockBean/@SpringBean that leads to slow build test phases (see Using @MockBean in tests forces reloading of Application Context or the problem with @MockBean)
JUnit5 示例。
首先,您有一些连接某些依赖对象的服务。
然后你有一些依赖对象,这里是一个存储库:
这就是 JUnit 测试的样子:
JUnit5 example.
First you have some service that wires in some dependent object.
Then you have some dependent object, here a Repository:
This is how the JUnit test would look:
我有一个复杂的 EventListeners 集成测试场景,在我的例子中,最简单、最快的方法是提供一个模拟 bean 或从上下文中丢弃它。
我最终选择通过以下方法放弃它。
我创建了一个 jUnit 扩展类:
我创建了一个自定义注释:
最后,我在集成测试类上使用了自定义注释:
它对我来说效果很好!
I had a complex EventListeners integration testing scenario, the simplest and fastest approach in my case was to provide a mocked bean or discard it from the context.
I ended up choosing to discard it with the following approach.
I create a jUnit extension class:
I created a custom annotation:
And finally, I used the custom annotation on my integration test class:
It's worked fine to me!