测试 Spring 引导命令行应用程序

Testing a Spring Boot command line application

我想测试我的 Spring 引导命令行应用程序。我想模拟某些 bean(我可以通过在测试 class 的顶部注释 @ContextConfiguration(classes = TestConfig.class) 来做到这一点。在 TestConfig.class 中,我覆盖了我想模拟的 bean 。我想 Spring Boot 找到其余的组件。这似乎可行。

问题是当我运行 测试时,整个应用程序正常启动(即调用run() 方法)。

@Component
public class MyRunner implements CommandLineRunner {

    //fields

    @Autowired
    public MyRunner(Bean1 bean1, Bean2 bean2) {
        // constructor code
    }

    @Override
    public void run(String... args) throws Exception {
        // run method implementation
    }

我试图覆盖 MyRunner @Bean 并将其放入 TestConfig.class,但这似乎不起作用。我知道我正在加载常规应用程序上下文,但这就是我想做的(我想?)因为我想重新使用我在我的应用程序,并且只模拟一小部分。

有什么建议吗?

编辑:

Application.java

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

CommandLineRunners是普通bean,有一个例外:

加载应用程序上下文后,spring boot 在其所有 bean 中查找实现此接口的 bean 并自动调用它们的 run 方法。

现在,我想请您执行以下操作:

  1. 从测试中删除 ContextConfiguration 并在 MyRunner 的构造函数中放置一个断点。测试应如下所示:
@RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
   @Autowired
   private MyRunner myRunner;
   @Test
   public void testMe() {
     System.out.println("hello");
   }
}
  1. 运行 测试并确保我的运行ner 已加载并且它的run 方法被调用
  2. 现在用 MockBean 注释模拟这个 class:
@RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {

   @MockBean
   private MyRunner myRunner;
   @Test
   public void testMe() {
     System.out.println("hello");
   }
}
  1. 运行 测试。确保 run 方法不是 运行ning。您的应用程序上下文现在应该包含组件的模拟实现。

  2. 如果上述方法有效,则问题出在 TestConfigContextConfiguration 注释上。一般来说,当你 运行 没有 ContextConfiguration 时,你给 spring 启动测试引擎一个自由来模仿应用程序上下文启动,就好像它是一个真正的应用程序(具有自动配置,属性 分辨率,递归 bean 扫描等)。 但是,如果您放置 ContextConfiguration,spring 启动测试不会像这样工作 - 相反它只会加载您在该配置中指定的 bean。没有自动配置,例如没有递归 bean 扫描。

更新

根据 OP 的评论:

看起来 MyRunner 在您放置 @ContextConfiguration 时加载,因为组件扫描。由于您在 MyRunner class 上放置了注释 @Component,Spring 引导引擎可以发现它。

事实上这里有两种类型的 beans 定义的混合 "dangerous": 1、在@Configuration注解中用@Bean定义的bean 2.组件扫描发现的bean

这里有一个问题要问你:如果你不想模仿应用程序的启动过程,而是更喜欢只加载特定的 bean,那你为什么要使用 @SpringBootTest?也许您可以通过以下方式实现目标:

@RunWith(SpringRunner.class)
@ContextConfiguration(YourConfig.class)
public class MyTest {
  ...
}

您可以这样做的一种方法是使用 main 方法有 2 个 classes,一个设置 "normal" 上下文,另一个设置 "mock" 上下文:

普通应用程序上下文,使用通常的 Application

@SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Foo foo() {
        return new Foo("I am not mocked");
    }

    @Bean
    public Bar bar() {
        return new Bar("this is never mocked");
    }
}

添加另一个 Application class 用模拟的

覆盖正常上下文
@SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
@Component
public class MockApplication {

    public static void main(String[] args) {

        SpringApplication.run(MockApplication.class, args);
    }

    @Bean
    public Foo foo() {
        return new Foo("I am mocked");
    }
}

当你 运行 Application.main Foo 将是 "I am not mocked",当你 运行 MockApplication.main() 它将是 "I am mocked"

答案比我想象的要简单。在

中添加 MockBean
@TestConfiguration
public class TestConfig {

    @MockBean
    private MyRunner myRunner;

}

我们可以使用@MockBean 将模拟对象添加到Spring 应用程序上下文。模拟将替换应用程序上下文中相同类型的任何现有 bean。

所以 MyRunner.run() 永远不会被调用,但我仍然可以在我的应用程序中使用所有其他 bean。