【发布时间】:2021-01-16 08:09:44
【问题描述】:
有没有一种通用的方法来替换 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 来定义?
我还想实现一些小型演示应用程序,用于与主应用程序一起测试 puproses。演示应用程序在测试源代码树中定义,但在自己的包中。他们需要使用主应用程序的配置并定义自己的 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
Test2 从 Eclipse 运行时为绿色,但如果通过 gradle 运行则失败:
org.junit.ComparisonFailure: expected:<bean2[ from TestConfig]> but was:<bean2[]>
所以,有一个很大的缺点......
【问题讨论】:
标签: java spring spring-boot spring-boot-test