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

80

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

针对我的要求,配置文件似乎过于复杂,并且我不确定是否可以通过Primary注释来实现,因为不同的单元测试可能具有不同的模拟。


2
你尝试过@ContexConfiguration吗?http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/testing.html#testcontext-ctx-management-javaconfig你想在不同的测试类中使用不同的模拟对象,还是在同一个测试类的不同测试方法中使用不同的模拟对象? - mats.nowak
是的,这正是我构想的,将我的标准配置与测试配置一起设置,并在测试配置中覆盖要覆盖的bean。为整个类设置不同的mock就足够了。 - samblake
这个回答解决了你的问题吗?在集成测试中覆盖Bean - Vadzim
6个回答

90
如果你只是想在测试中提供一个不同的 bean,我认为你不需要使用 Spring profiles 或 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 进行测试。


19
我得到了这样一条信息:"bean的定义已经存在。这个顶级bean定义被认为是一种覆盖。" 我必须把"myService()"方法改成其他名称,例如"myServiceMock()"。我还建议使用@Primary注释(对于IMyService),以确保在TestConfig配置中定义的bean将覆盖Application配置中的bean。 - Kacper86
1
@Kacper86 这是因为您正在使用Spring 4.1或更低版本,该错误已在4.2中修复。 - Pierre
1
@Import注解是用来做什么的?只有在测试配置中需要使用现有的bean时才需要吗? - augurar
4
@Kacper86: 添加“Primary”注解解决了Spring发现接口有两个实现的问题。谢谢! - user1697575
3
我还必须使用@Primary注解来覆盖原始Bean(spring-boot-starter-parent:2.0.0.RELEASE,spring-boot-starter-test:2.0.4.RELEASE);谢谢 @Kacper86。 - Murukan
显示剩余2条评论

52

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

4
MyApplication 是什么? - Krzysztof Krasoń
1
你应该将其替换为包含main()函数并带有@SpringBootApplication注解的邮件应用程序类的名称。 - Sergey Shcherbakov
2
如果您想用Mockito Mock替换bean,那么这是最好的答案。否则(例如,如果您想注入自定义对象),它没有帮助。 - augurar
2
为了让 @MockBean 生效,你需要使用 @TestExecutionListeners(MockitoTestExecutionListener.class)。一些其他的注解或配置也可以神奇地帮你完成这个任务。请查看文档 )) - gavenkoa

7

我曾遇到类似的问题,通过混合使用方法解决了这个问题。我发现这种方法更加有用和可重复使用。我为测试创建了一个Spring配置文件,并编写了一个配置类,在其中以非常简单的方式覆盖要模拟的bean:

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

    @MockBean
    private Producer kafkaProducer;

    @MockBean
    private SlackNotifier slackNotifier;

}

通过这种方式,我可以使用@Autowire来调用那些模拟的bean,并使用mockito在它们上面进行验证。其主要优点是现在所有的测试都无需任何测试更改即可轻松地获取模拟bean。已测试适用于:spring boot 1.4.2。

4

从Spring Boot 1.4.0开始,测试时不需要显式指定@Configuration注解,只需添加一个静态嵌套类并使用@TestConfiguration进行注释,并提供替代的@Bean注解和@Primary注解。

@TestConfiguration将被添加到主要的Spring Boot测试上下文中(这意味着您的生产bean仍将被创建),但会使用@Primary注解的@TestConfiguration中的bean,而非生产bean。


1
这个可以工作,但需要注意以下几点:即使是静态内部类,测试配置类也需要在测试中导入,例如 @Import(MyTest.TestConfig.class)。工厂方法的名称需要与生产配置中的不同(例如 @Configuration @Bean thing(); @TestConfiguration @Primary @Bean thingMock();)。 - salomvary

3

-1
正如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",显然是必需的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接