首页 文章

将Mockito模拟注入Spring bean

提问于
浏览
253

我想将一个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 回答

  • 9

    看看Springockito pace of developmentnumber 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.

  • 2
    @InjectMocks
    private MyTestObject testObject;
    
    @Mock
    private MyDependentObject mockedObject;
    
    @Before
    public void setup() {
            MockitoAnnotations.initMocks(this);
    }
    

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

  • 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

  • 1

    今天我发现我在Mockito beans 之前声明的 Spring 天环境未能加载 . 在移动模拟后,应用程序上下文已成功加载 . 照顾自己 :)

  • 2

    下面的代码适用于自动装配 - 它不是最短的版本,但它只适用于标准的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>
    
  • 1

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

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

    鉴于:

    @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 默认情况下)

  • 1

    从Spring 3.2开始,这不再是一个问题 . Spring现在支持自动装配通用工厂方法的结果 . 请参阅此博客文章中名为"Generic Factory Methods"的部分: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>
    
  • 60

    最好的方法是:

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

    Update
    在上下文文件中,必须在任何自动装配字段之前列出此模拟,具体取决于它的声明 .

  • 39

    由于1.8.3 Mockito有@InjectMocks - 这非常有用 . 我的JUnit测试是@RunWith MockitoJUnitRunner,我构建的@Mock对象满足所测试类的所有依赖关系,这些对象都是在私有成员使用@InjectMocks注释时注入的 .

    我@Run仅使用SpringJUnit4Runner进行集成测试 .

    我会注意到它似乎无法以与Spring相同的方式注入List . 它只查找满足List的Mock对象,并且不会注入Mock对象列表 . 我的解决方法是对手动实例化的列表使用@Spy,并手动将模拟对象添加到该列表以进行单元测试 . 也许这是故意的,因为它肯定迫使我密切关注被 Mock 的事情 .

  • 9

    我使用了Markus T回答的方法和 ImportBeanDefinitionRegistrar 的简单帮助器实现的组合,该实现查找自定义注释( @MockedBeans ),其中可以指定要模拟哪些类 . 我相信这种方法可以实现简洁的单元测试一些与mocking相关的样板代码被删除了 .

    以下是采用该方法的样本单元测试的外观:

    @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 方法中的配置中 .

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

  • 19

    我有一个使用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;
        }
    }
    
  • 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解释它 .

  • 119

    为了记录,我的所有测试只需使夹具延迟初始化即可正常工作,例如:

    <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声明的顺序 - 懒惰的初始化是"sort of",最后声明了夹具 .

  • 7

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

  • 11

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

  • -1

    如果你使用的是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 Profiles :

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

    如果您没有't want to use the mock but the real bean, just don' t激活 useMocks Profiles :

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

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

    随着 Spring 天:

    @ContextConfiguration(locations = { "classpath:context.xml" })
    @RunWith(SpringJUnit4ClassRunner.class)
    public class TestServiceTest {
        @InjectMocks
        private TestService testService;
        @Mock
        private TestService2 testService2;
    }
    

    没有 Spring 天:

    @RunWith(MockitoJUnitRunner.class)
    public class TestServiceTest {
        @InjectMocks
        private TestService testService = new TestServiceImpl();
        @Mock
        private TestService2 testService2;
    }
    
  • 100

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

  • 13

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

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

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

  • 0

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

  • 6

    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

    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>
    

相关问题