[英]SpringBoot replacing beans in tests
有没有一种通用的方法来替换 bean 进行测试? 是否可以在同一个项目中实现两个或更多的应用程序?
我尝试获得下一个配置:
我有一个带有配置好的 bean 的主要 SpringBoot 应用程序:
package org.application.app;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Service1 service1() {
return new Service1();
}
@Bean
public Service2 service2() {
return new Service2();
}
}
在测试中,我需要更改其中一个 bean 的配置,因此我需要替换 bean。 但我需要保留主应用程序的其余配置:
package org.application.app;
// @SpringBootTest // will give BeanDefinitionOverrideException
@SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
@RunWith(SpringRunner.class)
public class ApplicationTest {
@Test
public void test() {
}
@Configuration
@Import(Application.class)
public static class Config {
@Bean
public Service2 service2() {
return new Service2();
}
}
}
那么,第一个问题:如何避免allow-bean-definition-overriding
? 有没有通用的方法? 我尝试了不同的 bean 名称和@Primary
,但是如果我需要定义两个以上的替代 bean?
我还想实现一些小的演示应用程序,用于与主应用程序一起测试目的。 演示应用程序在测试源代码树中定义,但在自己的包中。 他们需要使用来自主应用程序的配置并定义自己的 bean,就像测试一样:
package org.application.demo;
@SpringBootApplication
@Import(Application.class)
public class ApplicationDemo {
public static void main(String[] args) {
SpringApplication.run(ApplicationDemo.class, args);
}
}
但是这个问题根本没有解决。 如果我运行ApplicationDemo
,我会得到关于ApplicationTest$Config
的异常:
The bean 'service2', defined in class path resource [org/application/app/ApplicationTest$Config.class], could not be registered. A bean with that name has already been defined in class path resource [org/application/app/Application.class] and overriding is disabled.
如何在ApplicationDemo
案例中从ComponentScan
中排除所有*Test
类?
我已经很困惑如何将所有这些情况联系起来,同时避免隐式覆盖 bean ......
更新
我会澄清这个问题。 有一个主要的应用程序配置。 测试配置继承自主配置并覆盖一些bean。 具体的测试配置继承自主测试配置,也覆盖了一些bean。 是否有实现这种模式的最佳实践?
使用配置文件似乎是这种模式的错误方式。
我最好的解决方案如下:
应用:
package beans.orig.bean;
// The bean should be manually configured
public class MyBean {
@Autowired
public MyBean bean1;
public String value;
public MyBean(String value) {
this.value = value;
}
}
// The component will be loaded by @ComponentScan
@Component
public class MyComponent {
@Autowired
public MyBean bean2;
}
package beans.orig;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
MyBean bean2 = (MyBean) ctx.getBean("bean2");
if (!bean2.value.equals("bean2")) {
throw new RuntimeException();
}
System.out.println("OK");
}
}
// Main application configuration.
// It could be inside the Application class, but then @Import(Application.class)
// in tests will trigger @ComponentScan, what to avoid sometimes.
@Configuration
public class ApplicationConfig {
@Bean
public MyBean bean1() {
return new MyBean("bean1");
}
@Bean
public MyBean bean2() {
return new MyBean("bean2");
}
}
和测试:
package beans.orig;
// Main test configuration.
// It uses main config with some overridden beans.
@Configuration
@Import(Application.class) // to allow @ComponentScan and so on...
public class TestConfig {
// Override bean2
@Bean
public MyBean bean2() {
return new MyBean("bean2 from TestConfig");
}
}
// Use virgin main configuration
@SpringBootTest
@RunWith(SpringRunner.class)
public class Test1 {
@Autowired
MyComponent comp;
// Prevent global @ComponentScan and scan only dedicated packages. It isn't
// clear how to avoid this, because otherwise bean2 will be used from
// TestConfig (or even any other configuration from tests).
@Configuration
@Import(ApplicationConfig.class)
@ComponentScan("beans.orig.bean")
public static class Config {
}
@Test
public void test() {
assertNotNull(comp);
assertEquals("bean1", comp.bean2.bean1.value);
assertEquals("bean2", comp.bean2.value);
}
}
// Use main test configuration. It is required to allow bean definition
// overriding. It isn't clear how to avoid this.
@SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
@RunWith(SpringRunner.class)
public class Test2 {
@Autowired
MyComponent comp;
@Test
public void test() {
assertNotNull(comp);
assertEquals("bean1", comp.bean2.bean1.value);
assertEquals("bean2 from TestConfig", comp.bean2.value);
}
}
// Use main test configuration. It is required to allow bean definition
// overriding. It isn't clear how to avoid this.
@SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
@RunWith(SpringRunner.class)
public class Test3 {
@Autowired
MyComponent comp;
@Configuration
@Import(TestConfig.class)
public static class Config {
@Bean
public MyBean bean2() {
return new MyBean("bean2 from Test");
}
}
@Test
public void test() {
assertNotNull(comp);
assertEquals("bean1", comp.bean2.bean1.value);
assertEquals("bean2 from Test", comp.bean2.value);
}
}
(请参阅github上的完整资源)
有什么缺点或改进吗?
更新 2
从 Eclipse 运行时, Test2
为绿色,但如果通过 gradle 运行则失败:
org.junit.ComparisonFailure: expected:<bean2[ from TestConfig]> but was:<bean2[]>
所以,有一个很大的缺点......
在测试中用另一个 bean 替换 bean 的一种方法是通过@Profile
将不同的 bean 与不同的配置文件相关联,然后您可以在测试中使用@ActiveProfiles
提及活动配置文件。 这将允许测试只考虑为您的特定配置文件激活的 bean。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Service1 service1() {
return new Service1();
}
@Profile("dev")
@Bean
public Service2 service2Dev() {
return new Service2(); // First implementation
}
@Profile("cloud")
@Bean
public Service2 service2Prod() {
return new Service2(); // Second implementation
}
}
如果您只想使用模拟而不是常规 bean 进行测试,那么您可以从spring 测试文档中查看@MockBean
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.