将 Mockito 模拟注入 Spring bean

发布于 2024-08-26 04:02:11 字数 1147 浏览 8 评论 0原文

我想将 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(25

秋凉 2024-09-02 04:02:11

最好的方法是:

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

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

The best way is:

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

Update
In the context file this mock must be listed before any autowired field depending on it is declared.

深府石板幽径 2024-09-02 04:02:11
@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

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

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

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.

浅笑依然 2024-09-02 04:02:11

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

I have a very simple solution using Spring Java Config and 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;
    }
}
终难愈 2024-09-02 04:02:11

给定:

@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 已修复此问题。

对于早期版本,解决方案是解开代理,如下所述: 事务注释避免服务被模拟(这是 ReflectionTestUtils.setField 现在默认执行的操作)

Given:

@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}

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.

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

Please note that before Spring 4.3.1, this method won't work with services behind a proxy (annotated with @Transactional, or Cacheable, 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)

梦亿 2024-09-02 04:02:11

如果您使用 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 配置文件:

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

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

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

}

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:

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}

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:

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

If you don't want to use the mock but the real bean, just don't activate useMocks profile:

@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() {
        ....
    }
}
北方的韩爷 2024-09-02 04:02:11

1.8.3 起,Mockito 具有 @ InjectMocks - 这非常有用。我的 JUnit 测试是 @RunWithMockitoJUnitRunner,我构建了 @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 the MockitoJUnitRunner 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 the SpringJUnit4Runner 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 the List, 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.

后来的我们 2024-09-02 04:02:11

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

我最终在 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>

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 of Object. 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. The FactoryBean interface allows Spring to query the type of objects created by the factory bean.

My mocked bean definition now looks like:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>
演出会有结束 2024-09-02 04:02:11

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

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:

In Spring 3.2, generic return types for factory methods are now
properly inferred, and autowiring by type for mocks should work as
expected. As a result, custom work-arounds such as a
MockitoFactoryBean, EasyMockFactoryBean, or Springockito are likely no
longer necessary.

Which means this should work out of the box:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
夜声 2024-09-02 04:02:11

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

If you're using spring >= 3.0, try using Springs @Configuration annotation to define part of the application context

@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {

    @Bean
    public ApplicationService applicationService() {
        return mock(ApplicationService.class);
    }

}

If you don't want to use the @ImportResource, it can be done the other way around too:

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

For more information, have a look at spring-framework-reference : Java-based container configuration

黄昏下泛黄的笔记 2024-09-02 04:02:11

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

Below code works with autowiring - it is not the shortest version but useful when it should work only with standard spring/mockito jars.

<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> 
酒浓于脸红 2024-09-02 04:02:11

也许不是完美的解决方案,但我倾向于不使用 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.

猫烠⑼条掵仅有一顆心 2024-09-02 04:02:11

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

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

I can do the following using Mockito:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>
素食主义者 2024-09-02 04:02:11

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

使用 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;
}

Posting a few examples based on the above approaches

With Spring:

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

Without Spring:

@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService = new TestServiceImpl();
    @Mock
    private TestService2 testService2;
}
与往事干杯 2024-09-02 04:02:11

更新 - 新答案: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

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>

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

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

Next update your spring config with the following:

<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>
七禾 2024-09-02 04:02:11

我使用了 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 需要将模拟 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:

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

To make this happen you need to define two simple helper classes - custom annotation (@MockedBeans) and a custom
ImportBeanDefinitionRegistrar implementation. @MockedBeans annotation definition needs to be annotated with @Import(CustomImportBeanDefinitionRegistrar.class) and the ImportBeanDefinitionRgistrar needs to add mocked beans definitions to the configuration in it's registerBeanDefinitions method.

If you like the approach you can find sample implementations on my blogpost.

执笔绘流年 2024-09-02 04:02:11

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

故人如初 2024-09-02 04:02: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 的注入。

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

<bean id="someFacade" class="nl.package.test.MockFactory">
    <property name="type" value="nl.package.someFacade"/>
</bean>

This also helps to prevent that Spring wants to resolve the injections from the mocked bean.

稀香 2024-09-02 04:02:11
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

如果在 XML 文件中首先/早期声明,则 this ^ 效​​果非常好。 Mockito 1.9.0/Spring 3.0.5

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

this ^ works perfectly well if declared first/early in the XML file. Mockito 1.9.0/Spring 3.0.5

ま昔日黯然 2024-09-02 04:02:11

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

}

我写了一篇帖子解释它。

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.

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

}

I have written a post explaining it.

戏蝶舞 2024-09-02 04:02:11

我建议将您的项目迁移到 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 your com.package.Dao

想念有你 2024-09-02 04:02:11

今天我发现我在 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 :)

策马西风 2024-09-02 04:02:11

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

<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 的声明顺序 - 懒惰初始化“有点”在最后声明了固定装置。

For the record, all my tests correctly work by just making the fixture lazy-initialized, e.g.:

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

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.

小猫一只 2024-09-02 04:02:11

如果您使用的是 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 上下文重新创建,从而导致构建测试阶段缓慢(请参阅< 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:

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

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)

闻呓 2024-09-02 04:02:11

JUnit5 示例。

首先,您有一些连接某些依赖对象的服务。

@Component
public class MyService {
   private final MyRepository repository;

   public MyService(MyRepository repository) {
      this.repository = repository;
   }

   public MyData serviceFindBy(String id) {
      return repository.findById(id).get();
   }
}

然后你有一些依赖对象,这里是一个存储库:

@Repository
public interface MyRepository extends MongoRepository<MyData, String> {
   Optional<MyData> findById(String id);
}

这就是 JUnit 测试的样子:

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(classes = {MyService.class})
@ExtendWith(MockitoExtension.class)
class MyServiceTest {

    @InjectMocks
    MyService myService;

    @MockBean
    MyRepository myRepository;

    @Test
    public void testFindById() {
        when(myRepository.findById(any())).thenReturn(new MyData("123"));
        MyData myData = myService.serviceFindBy("123");
        assertEquals("123", myData.getId());
    }
}

JUnit5 example.

First you have some service that wires in some dependent object.

@Component
public class MyService {
   private final MyRepository repository;

   public MyService(MyRepository repository) {
      this.repository = repository;
   }

   public MyData serviceFindBy(String id) {
      return repository.findById(id).get();
   }
}

Then you have some dependent object, here a Repository:

@Repository
public interface MyRepository extends MongoRepository<MyData, String> {
   Optional<MyData> findById(String id);
}

This is how the JUnit test would look:

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(classes = {MyService.class})
@ExtendWith(MockitoExtension.class)
class MyServiceTest {

    @InjectMocks
    MyService myService;

    @MockBean
    MyRepository myRepository;

    @Test
    public void testFindById() {
        when(myRepository.findById(any())).thenReturn(new MyData("123"));
        MyData myData = myService.serviceFindBy("123");
        assertEquals("123", myData.getId());
    }
}
悍妇囚夫 2024-09-02 04:02:11

我有一个复杂的 EventListeners 集成测试场景,在我的例子中,最简单、最快的方法是提供一个模拟 bean 或从上下文中丢弃它。

我最终选择通过以下方法放弃它。

我创建了一个 jUnit 扩展类:

public class DisableBeanExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(final ExtensionContext extensionContext) {
        final Class<?> testClass = extensionContext.getRequiredTestClass();
        final DisableBean disableBeanAnnotation = testClass.getAnnotation(DisableBean.class);

        if (disableBeanAnnotation != null) {
            final var applicationContext = (ConfigurableApplicationContext) SpringExtension.getApplicationContext(extensionContext);
            final var registry = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
            for (Class<?> classType : disableBeanAnnotation.classes()) {
                registry.removeBeanDefinition(StringUtils.uncapitalize(classType.getSimpleName()));
            }
        }
    }

}

我创建了一个自定义注释:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ExtendWith(DisableBeanExtension.class)
public @interface DisableBean {

    Class<?>[] classes() default {};

}

最后,我在集成测试类上使用了自定义注释:

// Other annotations to set up the test class have been omitted
@DisableBean(classes = {BeanToRemoveOne.class, BeanToRemoveTwo.class})
class MyIntegrationTest {

    // Other dependencies and scenarios to set up the test cases have been omitted

}

它对我来说效果很好!

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:

public class DisableBeanExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(final ExtensionContext extensionContext) {
        final Class<?> testClass = extensionContext.getRequiredTestClass();
        final DisableBean disableBeanAnnotation = testClass.getAnnotation(DisableBean.class);

        if (disableBeanAnnotation != null) {
            final var applicationContext = (ConfigurableApplicationContext) SpringExtension.getApplicationContext(extensionContext);
            final var registry = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
            for (Class<?> classType : disableBeanAnnotation.classes()) {
                registry.removeBeanDefinition(StringUtils.uncapitalize(classType.getSimpleName()));
            }
        }
    }

}

I created a custom annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ExtendWith(DisableBeanExtension.class)
public @interface DisableBean {

    Class<?>[] classes() default {};

}

And finally, I used the custom annotation on my integration test class:

// Other annotations to set up the test class have been omitted
@DisableBean(classes = {BeanToRemoveOne.class, BeanToRemoveTwo.class})
class MyIntegrationTest {

    // Other dependencies and scenarios to set up the test cases have been omitted

}

It's worked fine to me!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文