将Mockito模拟注入Spring bean


284

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

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

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


4
请看一下这个微小的小动物:bitbucket.org/kubek2k/springockito/wiki/Home
kubek2k 2011年

这是一种非常干净的方法-我喜欢!
teabot

2
您在Springockito注释中拥有我。
yihtserns 2012年


2
对于使用Spring 4. *的用户,截至2015年1月,该功能似乎不适用于最新的Spring Mockito版本,并且该项目似乎处于非活动状态。
Murali 2015年

Answers:


130

最好的方法是:

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

更新
在上下文文件中,必须在声明任何自动装配字段之前列出该模拟,然后再进行声明。


我收到一个错误:“创建名称为'mockito'的bean时出错:bean定义是抽象的”
tttppp 2010年

4
@amra:在这种情况下,spring无法推断返回对象的类型... stackoverflow.com/q/6976421/306488
lisak 2011年

7
不知道为什么这个答案这么高,导致产生的bean无法自动装配,因为它的类型错误。
azerole

4
如果在上下文文件中首先列出它,则可以自动装配(在声明任何依赖它的自动装配字段之前)
Ryan Walls

3
从3.2版本开始,bean的顺序不再重要。请参阅此博客文章中标题为“通用工厂方法”的部分:spring.io/blog/2012/11/07/…–
Ryan Walls

110
@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

这会将所有模拟对象注入到测试类中。在这种情况下,它将嘲笑对象注入testObject中。上面已经提到了,但这是代码。


1
如何存根特定的方法mockedObject
吉姆·霍尔登

@Teinacher when(mockedObject.execute).thenReturn(objToReturn); 您可以将其放在before方法中或测试方法中。
chaostheory

40
仅供参考:如果我要在MyTestObject中进行部分自动装配和部分模拟,则此方法将行不通。
raksja

9
我不知道为什么这个票数不高。如果我看到任何其他包含XML的答案,我将投掷。
MarkOfHall,2015年

3
为什么不使用Mockito.spy(...)mockedObject呢?然后使用when(mockedObject.execute).thenReturn(objToReturn)doReturn(objToReturn).when(mockedObject).execute()。第二个不要调用真实方法。您也可以查看Mockito.doCallRealMethod()文档
Tomasz Przybylski 2016年

63

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

4
由于某种原因,spring会尝试创建实际的bean(而不是模拟),并对此感到that恼……我在做什么错?
Daniel Gruszczyk

1
我有同样的问题
Korobko Alex

3
如果您要嘲笑一个类,则不是spring,而是mockito尝试实例化一个实际的bean。如果您有在测试中必须模拟的任何bean,则它们应该是接口的实现,并通过该接口注入。如果您然后模拟该接口(而不是类),则模仿不会尝试实例化该类。
Daniel Gruszczyk

7
有什么意义?为什么要添加带注释的字段和构造函数initMocks?为什么不只是return Mockito.mock(BeanA.class)进入getBeanA?这样更简单,代码更少。我想念什么?
奥列格

1
@Oleg听起来您有您自己的解决方案,您可能应该将其发布为答案,以便社区对此进行投票。
达伍德·伊本·卡里姆

48

鉴于:

@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修复

对于较早的版本,一种解决方案是按如下所述解开代理:事务注释避免服务被嘲笑(这是ReflectionTestUtils.setField默认情况下的做法)


双重@RunWith(SpringJUnit4ClassRunner.class),我为测试类使用了不同的注释(相同的运行器),但是这种方法对我有用,谢谢。
user1317422

1
“请注意,在Spring 4.3.1之前,此方法不适用于代理后面的服务(例如,以@Transactional或Cacheable注释)。这已由SPR-14050修复。” 我只是碰到了这个问题,直到发现这句话才得到任何线索。非常感谢!
snowfox

1
当您连接了整个应用程序上下文,并且出于测试目的,想要在上下文中的随机bean中插入模拟程序时,此解决方案就可以处理。为了避免在模块测试中对其他模块的REST调用,我使用了这个答案来模拟假客户端bean。当您在要测试的bean中而不是在Spring Application Configuration创建的bean中注入模拟时,只有InjectMock注释可以工作。
Andreas Lundgren

1
整整一天的时间都在努力使@MockBean工作而不重置上下文,然后遇到了这个问题。正是我需要的,加油。
Matt R

可以,但是请注意,替换字段可能不会由于缓存而重置,并且某些不相关的测试可能会中断。例如,在我的测试中,我用模拟模拟替换了密码编码器,由于授权失败,其他一些测试也失败了。
alextsil

36

如果您使用的是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将您的模拟注入Spring上下文的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() {
        ....
    }
}

5
这个答案应该放在顶部-Spring Boot中的@MockBean支持也可以在没有spring-boot的情况下使用。您只能在单元测试中使用它,因此它适用于所有spring应用!
bedrin

2
您还可以在bean定义方法上设置@Profile批注,以避免创建单独的配置类
marcin

好答案!我进行了一些更改,使其可以与我的老式web.xml和AnnotationConfigWebApplicationContext设置一起使用。必须使用@WebAppConfiguration代替@WebIntegrationTest@ContextHierarchy@ContextConfiguration代替@SpringApplicationConfiguration
UTF_or_Death

我必须@Primary为我的案例添加注释,因为@PostConstruct要模拟的a内有一个失败的调用,但是@PostConstruct的Bean是在模拟之前创建的,因此未使用模拟(直到我添加@Primary)。
helleye

19

1.8.3版开始, Mockito就具有了@InjectMocks–非常有用。我的JUnit测试是@RunWithMockitoJUnitRunner并且我构建的@Mock对象满足要测试的类的所有依赖关系,并在对私有成员进行注释时注入所有这些依赖关系@InjectMocks

@RunWithSpringJUnit4Runner集成测试只是现在。

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


是的,这是最好的方法。对于我来说,Springockito实际上并不会出于任何原因注入模拟。
chaostheory

13

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

最终,我在ronen的博客上找到了答案。我遇到的问题是由于方法Mockito.mock(Class c)声明了的返回类型Object。因此,Spring无法从工厂方法返回类型推断出bean类型。

Ronen的解决方案是创建一个FactoryBean返回模拟的实现。该FactoryBean接口允许Spring查询由工厂bean创建的对象的类型。

我的模拟bean定义现在看起来像:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

1
更新至Ronen解决方案的链接:narkisr.com/blog/2008/2647754885089732945
杰夫·马丁

我不明白,工厂方法的返回类型为Object ...但是amra的解决方案具有通用的返回类型,以便Spring可以识别它。但是amra的解决方案对我不起作用
lisak

这两种解决方案都没有,spring不会推断从factoryBean返回的bean的类型,因此没有[com.package.Dao]类型的匹配bean……
lisak 2011年


该链接实际上仍然有效:javadevelopmentforthemasses.blogspot.com/2008/07/…只需在浏览器中禁用链接重定向,您就会看到它,而不是被迫在他的新博客上查看404。
大约蓝色

12

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

9

下面的代码可用于自动装配-它不是最短的版本,但是当它仅适用于标准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/...
Holgzn

9

如果您使用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的容器配置


好一个。当我要测试的测试在实际测试用例中为@Autowired时,就使用了此功能。
enkor 2014年

8

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


3
我了解您的做法。但是,我发现自己处在庞大的传统代码库中,而这还不容易。
teabot

1
我发现当我需要测试很大程度上依赖于Spring方面/ AOP的代码时(例如,当测试Spring安全规则时),Mockito / Spring组合非常有用。尽管声称这种测试应该是集成测试是完全合理的。
拉尔斯·塔克曼

@Lars-同意-我要处理的测试也可以这样说。
teabot

7

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

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

1
感谢您的回答@Alexander。我可以问一下:是否正确接线?如果是这样,您正在使用哪个版本的Spring / Mockito?
teabot

6

根据上述方法发布一些示例

与春季:

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

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>

2

看看Springockito的发展速度未解决的问题数量,我现在有点担心将其引入我的测试套件堆栈中。实际上,最后一个版本是在Spring 4发行之前完成的,它带来了诸如“是否可以轻松地将其与Spring 4集成?”之类的问题。我不知道,因为我没有尝试过。如果需要在集成测试中模拟Spring bean,我更喜欢纯Spring方法。

有一个选项可以伪造仅具有纯Spring功能的Spring bean。您需要使用@Primary@Profile@ActiveProfiles为它的注解。我写了一篇有关该主题的博客文章。


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想要解决来自模拟豆的注入。


1
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

如果在XML文件中先声明/早声明,则此^效果很好。Mockito 1.9.0 /春季3.0.5


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需要在其registerBeanDefinitions方法中将模拟Bean定义添加到配置中。

如果您喜欢这种方法,可以在我的博客文章中找到示例实现


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();
    }

}

我写了一篇文章解释它。




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 在这里(在文章的底部)解释的,一种解决方法是更改​​声明bean的顺序-懒惰初始化是在末尾声明夹具的“某种”。


-1

如果您使用Controller Injection,请确保您的局部变量不是“最终”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.