SpringBoot在测试中替换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?

我还想与主应用程序一起实现一些用于测试人偶的小型演示应用程序。演示应用程序在测试源树中定义,但在自己的程序包中。他们需要使用主应用程序中的配置并定义自己的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.

*Test情况下,如何从ComponentScan中排除所有ApplicationDemo类?

我已经很困惑如何处理所有这些情况,同时避免隐式覆盖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[]>

所以,有很大的缺点...

解决方法

在测试中将一个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 testing documentation中检出@MockBean

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...