测试一个Spring Boot命令行应用程序

5
我想测试我的Spring Boot命令行应用程序。我希望模拟某些bean(通过在测试类的顶部注释@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 中,但似乎没有起作用。我知道我正在加载常规应用程序上下文,但这正是我想要做的事情(我认为?),因为我想重复使用我在应用程序中创建的所有(或大多数) @Component ,并仅模拟一小部分。
有什么建议吗?
编辑: Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

所以说,您想要从命令行运行,使用实际上是模拟某些 bean 的测试上下文?您能否将 Application 类(即您正在调用的带有主方法的类)添加到问题中? - stringy05
@stringy05 已经编辑了问题。基本上我只想编写一个测试类,在其中可以注入所有组件(除了一些需要模拟的组件)。问题是当我运行测试方法时,MyRunner 被注入并像应用程序启动时一样运行,而无论出于什么原因模拟该类都无法覆盖它。 - Tiberiu
3个回答

2
答案比我想象的简单。在代码中添加MockBean即可。
@TestConfiguration
public class TestConfig {

    @MockBean
    private MyRunner myRunner;

}

我们可以使用@MockBean将模拟对象添加到Spring应用程序上下文中。该模拟对象将替换应用程序上下文中相同类型的任何现有bean。
因此,MyRunner.run()从未被调用,但我仍然可以使用应用程序中的所有其他bean。

1

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. 运行测试并确保myRunner已加载并调用其run方法
  2. 现在使用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方法未运行。您的应用程序上下文现在应该包含您组件的模拟实现。

  2. 如果上述方法有效,则问题可能出在TestConfigContextConfiguration注释上。一般来说,如果您没有使用ContextConfiguration运行,Spring Boot测试引擎会自由地模拟启动应用程序上下文,仿佛它是一个真正的应用程序(包括自动配置、属性解析、递归Bean扫描等)。 但是,如果您放置了ContextConfiguration,那么Spring Boot测试将不会像这样工作——相反,它只会加载您在该配置中指定的Bean。例如,不会进行自动配置和递归Bean扫描。

更新

根据发帖者的评论:

看起来,当您添加@ContextConfiguration时,MyRunner会因为组件扫描而被加载。由于您在MyRunner类上放置了一个@Component注释,因此它可以被Spring Boot引擎发现。

事实上,这里存在两种类型的bean定义的“危险”混合: 1. 在@Configuration注释中使用@Bean定义的bean 2. 在组件扫描期间找到的bean。
这里有一个问题:如果您不想模拟应用程序启动过程,而是更喜欢仅加载特定的bean,则为什么还要使用@SpringBootTest?也许您可以通过以下方式实现目标:
@RunWith(SpringRunner.class)
@ContextConfiguration(YourConfig.class)
public class MyTest {
  ...
}

问题在于当我删除@ContextConfiguration时,它会选择所有的应用程序bean。TestConfig.class是为了我的bean覆盖。如果我保留@ContextConfiguration,应用程序bean实际上都被正确加载(包括我想要模拟的那些),但是CommandLineRunner bean也被加载,然后运行整个应用程序。 - Tiberiu
注意,我已经更新了这个答案,请尝试我的建议并进行更新。 - Mark Bramnik
感谢所有的帮助。最终我找到了一个相当简洁的解决方案(请参见我的回答)。 - Tiberiu
很酷,很高兴你找到了解决方案。顺便说一下,你也可以直接在测试中使用@MockBean。 :) - Mark Bramnik
谢谢!我相信你的最后一个例子是做这件事的最佳方式。通过谷歌搜索很难找到它,因为有太多关于如何做这个的迭代。 - Daniel Kaplan

0
你可以通过创建两个带有主方法的类来实现这一点,一个设置“正常”上下文,另一个设置“模拟”上下文:
“正常”应用程序上下文使用通常的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类,用模拟上下文覆盖正常上下文。
@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"。

这表示: java.lang.IllegalStateException:找到多个带有@SpringBootConfiguration注解的类 - Tiberiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接