【问题标题】:SpringBoot replacing beans in testsSpringBoot 在测试中替换 bean
【发布时间】: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


    【解决方案1】:

    在您的测试中用另一个 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

    【讨论】:

    • 使用配置文件似乎是这种模式的错误方式。但也许我错了。我已经更新了问题。
    • 我认为添加测试特定配置也是一种有效的方法,但我认为您正在寻找一种方法来做到这一点,而不会弄脏应用程序上下文并且不必使用不同的配置重新加载整个应用程序上下文。如果是这种情况,那么所有可能的 bean 必须存在于相同的上下文配置中,并且您必须在运行时选择正确的 bean。然后,bean 鉴别器将取决于您的业务逻辑。希望这会有所帮助。
    猜你喜欢
    • 2015-10-28
    • 2020-07-02
    • 2019-06-24
    • 1970-01-01
    • 2016-10-09
    • 2019-03-23
    • 2018-07-31
    • 2018-07-06
    • 1970-01-01
    相关资源
    最近更新 更多