在单元测试中覆盖自动装配的 Bean

Posted

技术标签:

【中文标题】在单元测试中覆盖自动装配的 Bean【英文标题】:Overriding an Autowired Bean in Unit Tests 【发布时间】:2015-04-20 18:22:36 【问题描述】:

有没有一种简单的方法可以在特定的单元测试中轻松覆盖自动装配的 bean?编译类中每种类型只有一个 bean,因此在这种情况下自动装配不是问题。测试类将包含额外的模拟。在运行单元测试时,我只想指定一个附加的配置,基本上说,在运行这个单元测试时使用这个模拟而不是标准 bean。

配置文件对于我的要求来说似乎有点矫枉过正,我不确定这是否可以通过 Primary 注释来实现,因为不同的单元测试可能有不同的模拟。

【问题讨论】:

你试过@ContexConfiguration吗? docs.spring.io/spring/docs/3.2.x/spring-framework-reference/…你想在同一个测试类中对不同的测试类使用不同的mock,还是对不同的测试方法使用不同的mock? 是的,这就是我的设想,将我的标准配置与测试配置一起设置,并在测试配置中覆盖要覆盖的 bean。整个班级的不同模拟就足够了。 这能回答你的问题吗? Overriding beans in Integration tests 【参考方案1】:

您应该使用 spring 配置文件以了解您想在不同的上下文中使用哪种 bean。

http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html

【讨论】:

【参考方案2】:

如果您只是想在测试中提供不同的 bean,我认为您不需要使用 spring 配置文件或 mockito。

只需执行以下操作:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes =  TestConfig.class )
public class MyTest

    @Configuration
    @Import(Application.class) // the actual configuration
    public static class TestConfig
    
        @Bean
        public IMyService myService()
        
            return new MockedMyService();
        
    

    @Test
    public void test()
    
        ....
    

注意:使用 spring boot 1.3.2 / spring 4.2.4 测试

【讨论】:

我得到“bean 的定义......已经存在。这个*** bean 定义被认为是覆盖。”我不得不将“myService()”方法更改为其他名称,例如“我的服务模拟()”。我还建议使用@Primary 注释(用于 IMyService)以确保 TestConfig 配置中定义的 bean 将覆盖 Application 配置中的那个。 @Kacper86 因为您使用的是 Spring 4.1 或更低版本,所以该错误已在 4.2 中修复 @Import 注释是干什么用的?仅当您想在测试配置中使用现有 bean 时才需要这样做吗? @Kacper86:添加“Primary”注释解决了 Spring 关于找到 2 个接口实现的抱怨。谢谢! 我还必须使用 @Primary 注释来覆盖原始 bean (spring-boot-starter-parent:2.0.0.RELEASE, spring-boot-starter-test:2.0.4.发布);谢谢@Kacper86【参考方案3】:

在 Spring Boot 1.4 中有一种简单的方法可以做到这一点:

@RunWith(SpringRunner.class)
@SpringBootTest(classes =  MyApplication.class )
public class MyTests 
    @MockBean
    private MyBeanClass myTestBean;

    @Before
    public void setup() 
         ...
         when(myTestBean.doSomething()).thenReturn(someResult);
    

    @Test
    public void test() 
         // MyBeanClass bean is replaced with myTestBean in the ApplicationContext here
    

【讨论】:

什么是MyApplication 您应该将其替换为包含 main() 函数并使用 @SpringBootApplixation 注释的邮件应用程序类的名称 如果你想用 Mockito mock 替换 bean,这是最好的答案。否则(例如,如果你想注入自定义对象)它没有帮助。 要使@MockBean 工作,您需要@TestExecutionListeners(MockitoTestExecutionListener.class)。其他一些注释或配置可以神奇地为您完成。检查文档))【参考方案4】:

我遇到了类似的问题,我通过混合解决了这个问题,我发现这个问题更有用且可重复使用。我为测试创建了一个 spring 配置文件和一个配置类,它以一种非常简单的方式覆盖了我想模拟的 bean:

@Profile("test")
@Configuration
@Import(ApplicationConfiguration.class)
public class ConfigurationTests 

    @MockBean
    private Producer kafkaProducer;

    @MockBean
    private SlackNotifier slackNotifier;


通过这样做,我可以@Autowire 那些模拟 bean 并使用 mockito 来验证它们。主要优点是现在所有测试都可以无缝地获取模拟 bean,而无需对每个测试进行任何更改。 测试:

弹簧靴 1.4.2

【讨论】:

【参考方案5】:

正如 mats.nowak 评论的那样,@ContextConfiguration 对此很有用。

假设一个父测试类是这样的:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/some-dao-stuff.xml"
    ,"classpath:spring/some-rest-stuff.xml"
    ,"classpath:spring/some-common-stuff.xml"
    ,"classpath:spring/some-aop-stuff.xml"
    ,"classpath:spring/some-logging-stuff.xml"
    ,"classpath:spring/some-services-etc.xml"
)
public class MyCompaniesBigTestSpringConfig 
...

创建一个子测试类:

package x.y.z;
@ContextConfiguration
public class MyOneOffTest extends MyCompaniesBigTestSpringConfig 
...

并放入 src/test/resources/x/y/z/MyOneOffTest-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">


    <bean id="widgetsService" class="com.mycompany.mydept.myservice.WidgetsService" primary="true" />

</beans>

widgetsService bean 将覆盖(取代)主配置 xml(或 Java 配置)中定义的 bean。见inheritLocations 另请注意默认的 -context.xml 文件。 here 的例子。 更新:我必须添加primary="true",显然是需要的。

【讨论】:

【参考方案6】:

从 Spring Boot 1.4.0 开始,无需为测试显式指定 @Configuration,只需添加带有 @TestConfiguration 注释的静态嵌套类,并提供带有 @Primary 注释的替换 @Bean

@TestConfiguration 将被添加到您的主要 Spring Boot 测试上下文中(这意味着您的生产 bean 仍将被创建),但将使用来自 @TestConfiguration 的那个,因为 @Primary

【讨论】:

这行得通,有几点说明:测试配置类需要在测试中导入,即使它是静态内部类,例如。 @Import(MyTest.TestConfig.class)。工厂方法的命名需要与生产配置中的不同(例如@Configuration @Bean thing(); @TestConfiguration @Primary @Bean thingMock();

以上是关于在单元测试中覆盖自动装配的 Bean的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 应用程序的 JUnit 测试中,自动装配的 JPA 存储库没有合格的 bean

Spring之Bean的自动装配

SpringBean自动装配

Spring学习Bean自动装配与注解开发

intellij 错误地说没有为自动装配的存储库找到类型的 bean

使用@SpringBootTest时如何在测试类中自动装配bean