ChatGPT解决这个技术问题 Extra ChatGPT

将 Mockito 模拟注入 Spring bean

为了使用 JUnit 进行单元测试,我想将 Mockito 模拟对象注入 Spring (3+) bean。我的 bean 依赖项当前是通过使用私有成员字段上的 @Autowired 注释注入的。

我考虑过使用 ReflectionTestUtils.setField,但我希望注入的 bean 实例实际上是一个代理,因此没有声明目标类的私有成员字段。我不希望为依赖项创建一个公共设置器,因为我将纯粹为了测试目的而修改我的接口。

我遵循了 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 值设置为无效值,则启动应用程序上下文时不会发生错误。

请看看这个小小的生物:bitbucket.org/kubek2k/springockito/wiki/Home
这是一个非常干净的方法 - 我喜欢它!
你让我在 Springockito-annotations。
对于使用 spring 4.* 的用户,截至 2015 年 1 月,这似乎不适用于最新的 spring mockito 版本,并且该项目似乎处于非活动状态。

a
amra

最好的方法是:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</bean> 

更新在上下文文件中,这个模拟必须在任何依赖于它的自动装配字段被声明之前列出。


@amra:spring 不会推断在这种情况下返回的对象的类型... stackoverflow.com/q/6976421/306488
不知道为什么这个答案如此受欢迎,生成的 bean 无法自动装配,因为它的类型错误。
如果它在上下文文件中首先列出,则可以自动装配(在声明任何依赖它的自动装配字段之前)。
从 spring 3.2 开始,bean 的顺序不再重要。请参阅此博文中标题为“通用工厂方法”的部分:spring.io/blog/2012/11/07/…
问题说OP已经尝试过了,但没有奏效。答案只是重复OP已经在做的事情是没有意义的。是否有超过 100 名支持者费心阅读这个问题?
J
JonyD
@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

这会将任何模拟对象注入测试类。在这种情况下,它会将 mockedObject 注入到 testObject 中。这是上面提到的,但这里是代码。


如何存根 mockedObject 的特定方法?
仅供参考:如果我想在 MyTestObject 中进行部分自动装配和部分模拟,这种方法将不起作用。
这正是我正在寻找的解决方案类型。非常简单优雅,可以完成工作。不知道为什么没有更多的赞成票。
我不知道为什么这没有得到更高的投票。如果我看到更多包含 XML 的答案,我将投掷。
为什么不在这个 mockedObject 上使用 Mockito.spy(...)?然后使用 when(mockedObject.execute).thenReturn(objToReturn)doReturn(objToReturn).when(mockedObject).execute()。第二个不调用真正的方法。您还可以查看 Mockito.doCallRealMethod() 文档
P
Piotr Gwiazda

我有一个使用 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;
    }
}

出于某种原因,使用这种方法,spring 无论如何都会尝试创建实际的 bean(而不是 mock)并且对此感到窒息......我做错了什么?
我有同样的问题
如果你在模拟一个类,不是 spring 而是 mockito 尝试实例化一个实际的 bean。如果您有任何必须在测试中模拟的 bean,它们应该是接口的实现,并通过该接口注入。如果您随后模拟接口(而不是类),mockito 将不会尝试实例化该类。
重点是什么?为什么要使用 initMocks 添加带注释的字段和构造函数?为什么不只是 getBeanA 中的 return Mockito.mock(BeanA.class) ?这种方式更简单,代码更少。我错过了什么?
@Oleg 听起来您有自己的解决方案,您可能应该将其发布为答案,以便社区可以对其进行投票。
P
Prags

鉴于:

@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 之前,此方法不适用于代理后面的服务(例如,使用 @TransactionalCacheable 注释)。 SPR-14050 已解决此问题。

对于早期版本,解决方案是解包代理,如下所述:Transactional annotation avoids services being mocked(这是 ReflectionTestUtils.setField 现在默认执行的操作)


Double @RunWith(SpringJUnit4ClassRunner.class) 并且我对测试类(相同的运行器)使用不同的注释,但是这种方法对我有用,谢谢。
“请注意,在 Spring 4.3.1 之前,此方法不适用于代理后面的服务(例如,使用 @Transactional 或 Cacheable 注释)。这已由 SPR-14050 修复”,我深受启发。我刚刚遇到了这个问题,直到发现这个词才得到任何线索。多谢!
当您连接了整个应用程序上下文并且出于测试目的想要在您的上下文中的随机 bean 中注入模拟时,此解决方案可以处理。我使用这个答案来模拟一个 feign 客户端 bean,以避免在模块测试中对其他模块进行 REST 调用。我只有在您将要测试的 bean 中注入模拟时才使 InjectMock 注释起作用,而不是在 Spring 应用程序配置创建的 bean 中。
几乎一整天都在试图让@MockBean 在不重置上下文的情况下工作,然后我遇到了这个宝石。正是我需要的,干杯。
有效,但请注意替换的字段可能由于缓存而不会重置,并且一些不相关的测试可能会中断。例如,在我的测试中,我用模拟的密码编码器替换了密码编码器,其他一些测试由于授权失败而中断。
j
jfcorugedo

如果您使用的是 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 或者你使用的是以前的版本,你将不得不做更多的工作:

创建一个将您的模拟注入 Spring 上下文的 @Configuration bean:

@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() {
        ....
    }
}

这个答案应该是最重要的——spring boot 中的@MockBean 支持也可以在没有 spring-boot 的情况下使用。您只能在单元测试中使用它,因此它适用于所有 Spring 应用程序!
@Profile 注解你也可以在 bean 定义方法上设置,以避免创建单独的配置类
很好的答案!我进行了一些更改以使其与我的老式 web.xml 和 AnnotationConfigWebApplicationContext 设置一起使用。必须使用 @WebAppConfiguration 而不是 @WebIntegrationTest@ContextHierarchy@ContextConfiguration 而不是 @SpringApplicationConfiguration
我必须为我的案例添加 @Primary 注释,因为在我想模拟的 @PostConstruct 内有一个失败的调用,但是 @PostConstruct 的 bean 是在我的模拟之前创建的,所以它没有使用模拟(直到我添加 @Primary)。
@MockBean 有一个明显的缺点:它可能会导致在构建测试阶段重新创建 Spring 上下文。 (见 stackoverflow.com/questions/45587213/…the problem with @MockBean)。您可以使用 @MockInBean 作为不重置上下文的 @MockBean 的替代方法。见my answer
I
Ihor Patsian

由于 1.8.3 Mockito 有 @InjectMocks - 这非常有用。我的 JUnit 测试是 @RunWith MockitoJUnitRunner 并且我构建了 @Mock 对象,这些对象满足正在测试的类的所有依赖项,当私有成员使用 @InjectMocks 注释时,这些对象全部被注入。

我现在只@RunWith SpringJUnit4Runner 进行集成测试。

我会注意到它似乎无法以与 Spring 相同的方式注入 List<T>。它只查找满足 List 的 Mock 对象,不会注入 Mock 对象列表。我的解决方法是对手动实例化的列表使用 @Spy,然后手动将模拟对象添加到该列表以进行单元测试。也许这是故意的,因为它确实迫使我密切关注一起被嘲笑的东西。


是的,这是最好的方法。在我的情况下,Springockito 实际上并没有出于任何原因注入模拟。
t
teabot

更新:现在有更好、更清洁的解决方案来解决这个问题。请先考虑其他答案。

我最终在他的博客上找到了 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>

更新了 Ronen 解决方案的链接:narkisr.com/blog/2008/2647754885089732945
我不明白,工厂方法的返回类型是 Object ......但是 amra 的解决方案有一个通用的返回类型,所以 Spring 应该识别它......但是 amra 的解决方案对我不起作用
这两种解决方案都没有,spring 不会推断出从 factoryBean 返回的 bean 的类型,因此没有匹配类型 [com.package.Dao] 的 bean ...
此链接实际上仍然有效:javadevelopmentforthemasses.blogspot.com/2008/07/… 只需在您的浏览器中禁用链接重定向,您就会看到它,而不是被迫查看他的新博客上的 404。
R
Ryan Walls

从 Spring 3.2 开始,这不再是问题。 Spring 现在支持自动装配通用工厂方法的结果。请参阅此博文中标题为“通用工厂方法”的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/

关键点是:

在 Spring 3.2 中,工厂方法的通用返回类型现在可以正确推断,并且模拟的按类型自动装配应该可以按预期工作。因此,可能不再需要 MockitoFactoryBean、EasyMockFactoryBean 或 Springockito 等自定义解决方法。

这意味着这应该开箱即用:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

P
Prags

如果您使用 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>

有关更多信息,请查看 spring-framework-reference:Java-based container configuration


好东西。当我正在测试的测试在实际测试用例中是 @Autowired 时,我使用了它。
C
Community

下面的代码适用于自动装配 - 它不是最短的版本,但当它只适用于标准 spring/mockito jar 时很有用。

<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> 

为我工作。我必须在我的测试中解开代理,以便按照此处所述进行验证:forum.spring.io/forum/spring-projects/aop/…
A
Angelo Genovese

也许不是完美的解决方案,但我倾向于不使用 spring 为单元测试做 DI。单个 bean(被测类)的依赖关系通常不会过于复杂,所以我只是直接在测试代码中进行注入。


我理解你的做法。但是,我发现自己在一个大型遗留代码库中处于这种情况,而这并不容易实现这一点。
当我需要测试严重依赖于 Spring 方面/AOP 的代码时(例如,在测试 Spring 安全规则时),我发现 Mockito/Spring 组合非常有用。尽管声称此类测试应该是集成测试是完全合理的。
@Lars - 同意 - 我正在处理的测试也是如此。
R
ROMANIA_engineer

我可以使用 Mockito 执行以下操作:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>

感谢@Alexander 的回答。请问:接线正确吗?如果是这样,您使用的是哪个版本的 Spring/Mockito?
我想要一个间谍而不是一个模仿者。怎么做 ?
B
Basu

基于上述方法发布一些示例

带弹簧:

@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;
}

C
Community

更新 - 这里有新的答案:https://stackoverflow.com/a/19454282/411229。此答案仅适用于 3.2 之前的 Spring 版本。

我已经寻找了一段时间来寻找更明确的解决方案。这篇博文似乎涵盖了我的所有需求,并且不依赖于 bean 声明的顺序。所有功劳归功于马蒂亚斯·塞弗森。 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>

K
Krešimir Nesek

我结合使用 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 需要在其 registerBeanDefinitions 方法中将模拟 bean 定义添加到配置中。

如果您喜欢这种方法,您可以在我的 blogpost 上找到示例 implementations


你让我今天一整天都感觉很好。整洁的。
l
luboskrnac

看看 Springockito pace of developmentnumber of open issues,我现在有点担心将它引入我的测试套件堆栈。事实上,上一个版本是在 Spring 4 发布之前完成的,这会引发诸如“是否可以轻松地将其与 Spring 4 集成?”之类的问题。我不知道,因为我没试过。如果我需要在集成测试中模拟 Spring bean,我更喜欢纯 Spring 方法。

可以选择仅使用普通 Spring 功能来伪造 Spring bean。您需要为其使用 @Primary@Profile@ActiveProfiles 注释。 I wrote a blog post on the topic.


博文链接 404s
抱歉,链接编号有错误,现已修复。
R
Renso Lohuis

我找到了一个与 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 的注入。


a
almondandapricot
<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


A
Alfredo Diaz

我根据 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来解释它。


l
luboskrnac

我建议将您的项目迁移到 Spring Boot 1.4。之后,您可以使用新注释 @MockBean 来伪造您的 com.package.Dao


D
Daniele Dellafiore

今天我发现我在 Mockito bean 之前声明的 spring 上下文无法加载。移动 AFTER 模拟后,应用程序上下文已成功加载。小心 :)


有什么东西不见了。 8-) 你在模拟之后移动了什么?
F
Fabrizio Giudici

作为记录,我所有的测试都通过使夹具延迟初始化来正确工作,例如:

<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 的顺序 - 延迟初始化“有点”在最后声明了夹具。


A
Antoine Meyer

如果您使用的是 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 Contextthe problem with @MockBean