繁体   English   中英

SpringBoot 在测试中替换 bean

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM