简体   繁体   English

测试 Spring Boot 命令行应用程序

[英]Testing a Spring Boot command line application

I'd like to test my Spring Boot command line application.我想测试我的 Spring Boot 命令行应用程序。 I would like to mock certain beans (which I was able to do by annotating @ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class , I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.我想模拟某些 bean(我可以通过在测试类的顶部注释@ContextConfiguration(classes = TestConfig.class)来完成。在TestConfig.class ,我覆盖了我想模拟的 bean。我想要Spring Boot找到其余的组件。这似乎有效。

The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).问题是当我运行测试时,整个应用程序正常启动(即调用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
    }

I've tried to override the MyRunner @Bean and put it in TestConfig.class , but that doesn't seem to work.我试图覆盖MyRunner @Bean并将其放入TestConfig.class ,但这似乎不起作用。 I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the @Component I created in my Application, and only mock a tiny subset.我知道我正在加载常规应用程序上下文,但这就是我想要做的(我认为?),因为我想重新使用我在应用程序中创建的所有(或大部分) @Component ,并且只模拟一个很小的子集。

Any suggestions?有什么建议?

EDIT:编辑:

Application.java应用程序.java

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

The answer was simpler than I thought.答案比我想象的要简单。 Add the MockBean in将 MockBean 添加到

@TestConfiguration
public class TestConfig {

    @MockBean
    private MyRunner myRunner;

}

We can use the @MockBean to add mock objects to the Spring application context.我们可以使用@MockBean 将模拟对象添加到 Spring 应用程序上下文中。 The mock will replace any existing bean of the same type in the application context.模拟将替换应用程序上下文中相同类型的任何现有 bean。

So MyRunner.run() is never called but I can still use all the other beans in my application.所以MyRunner.run()永远不会被调用,但我仍然可以在我的应用程序中使用所有其他 bean。

CommandLineRunners are ordinary beans with one exception: CommandLineRunners是普通的 bean,只有一个例外:

After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.加载应用程序上下文后,spring boot 会在其所有 bean 中找到实现此接口的 bean,并自动调用它们的run方法。

Now, I would like you to ask to do the following:现在,我想请您执行以下操作:

  1. Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner .从测试中移除ContextConfiguration并在MyRunner构造函数中放置一个断点。 The test should look like this:测试应如下所示:
@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. Run the test and make sure that myRunner is loaded and its run method is called运行测试并确保加载了 myRunner 并调用了它的run方法
  2. Now mock this class with MockBean annotation:现在用 MockBean 注释模拟这个类:
@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 the test.运行测试。 Make sure that run method is not running.确保run方法没有运行。 Your Application Context now should contain a mock implementation of your component.您的应用程序上下文现在应该包含您的组件的模拟实现。

  2. If the above works, then the problem is with TestConfig and ContextConfiguration annotation.如果上述方法有效,则问题出在TestConfigContextConfiguration注释上。 In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).通常,当您在没有ContextConfiguration情况下运行时,您可以让 Spring Boot 测试引擎自由地模拟启动的应用程序上下文,就好像它是一个真正的应用程序(具有自动配置、属性解析、递归 bean 扫描等)。 However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration.但是,如果您放置 ContextConfiguration,spring boot 测试不会像这样工作 - 相反,它只会加载您在该配置中指定的 bean。 No Autoconfigurations, no recursive bean scanning happens for example.例如,没有自动配置,没有递归 bean 扫描。

Update更新

Based on OP's comment:根据 OP 的评论:

It looks like the MyRunner gets loaded when you put @ContextConfiguration because of component scanning.由于组件扫描,当您放置 @ContextConfiguration 时,看起来MyRunner会被加载。 Since you have an annotation @Component placed on MyRunner class it can be discovered by Spring boot engine.由于您在MyRunner类上放置了一个注解@Component ,它可以被 Spring 引导引擎发现。

In fact there is a "dangerous" mix of two types of beans definitions here: 1. The beans defined with @Bean in @Configuration annotation 2. The beans found during component scanning.事实上,这里有两种类型的 bean 定义的“危险”混合: 1. 在@Configuration注释中用@Bean定义的 bean 2. 在组件扫描期间发现的 bean。

Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use @SpringBootTest at all?这是给你的问题:如果你不想模仿应用程序启动过程,而是更喜欢只加载特定的 bean,你为什么要使用@SpringBootTest Maybe you can achieve the goal with:也许您可以通过以下方式实现目标:

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

One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:一种方法是在 main 方法中使用 2 个类,一个设置“正常”上下文,另一个设置“模拟”上下文:

Normal App Context, uses the usual Application普通应用上下文,使用普通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");
    }
}

Add another Application class that overrides the normal context with the mocked one添加另一个Application类,该类使用模拟的覆盖正常上下文

@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");
    }
}

When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"当您运行Application.main Foo 将是“我没有被嘲笑”,当您运行MockApplication.main()它将是“我被嘲笑”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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