Spring 仅使用一个 Bean 进行上下文测试

Spring Context Test With Just One Bean

运行 spring 启动测试的推荐方法是什么,其中只有一个被测对象在上下文中配置。

如果我用

注释测试
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.profiles.active=test")
@ContextConfiguration(classes = MyTestBean.class)

然后它似乎工作了 - 测试通过,上下文快速启动并且似乎只包含我想要的 bean。但是,这似乎是对 @ContextConfiguration(classes = MyTestBean.class) 注释的错误使用。如果我理解正确,我引用的 class 应该是 Configuration class,而不是常规的 spring 服务 bean 或组件。

是吗?或者这确实是实现此目标的有效方法?我知道有更复杂的示例,例如 org.springframework.boot.test.autoconfigure.json.JsonTest,它们使用 @TypeExcludeFilters(JsonExcludeFilter.class) 来控制上下文——但这对于我的用例来说似乎有点过分了。我只想要我的一个 bean 的上下文。

澄清

我知道我可以在没有 spring 上下文测试的情况下将我正在测试的一个 bean 构建为 POJO,并删除上面的三个注释。但在我的精确用例中,我实际上依赖于 application-test.properties 文件中的设置应用于上下文的一些配置 - 这就是为什么我将其设为 Spring 带有配置文件集的启动测试.从我的角度来看,这不是孤立于 spring 上下文配置的单个 class 的普通单元测试 - 该测试依赖于所应用的某些配置(目前由 spring 启动应用程序属性)。我确实可以通过在 spring 上下文之外创建一个新实例来将组件作为 POJO 进行测试,我正在使用构造函数注入来简化必要依赖项的提供,但测试确实依赖于诸如日志级别之类的东西(该测试实际上对正在生成的某些日志做出断言),这需要正确设置日志级别(目前正在通过设置 spring 上下文的属性文件中的 logging.level.com.example=DEBUG 完成)。

在单元测试中只测试一个class绝对是合理和正常的事情。

在您的测试上下文中只包含一个 bean 是没有问题的。实际上,@Configuration(通常)只是 collection 个 bean。您可以假设只使用 MyTestBean 创建一个 @Configuration class,但这实际上是不必要的,因为您可以完成使用 @ContextConfiguration#classes 列出上下文 bean 的相同操作。 =15=]

但是,我确实想指出,对于在真正的单元测试中仅测试单个 bean,最佳实践理想地倾向于通过构造函数设置 bean 并以这种方式测试 class。这是 Spring 人推荐使用构造函数而不是 属性 注入的一个关键原因。有关详细信息,请参阅标题为 Constructor-based 或 setter-based DI of this article, Oliver Gierke's comment (i.e. head of Spring Data project), and google 的部分。这可能是您对为一个 bean 设置上下文感到奇怪的原因!

对于初学者来说,首先阅读文档(例如,此答案下方链接的 JavaDoc)是推荐的最佳做法,因为它已经回答了您的问题。

If I understand correctly the class that I reference is supposed to be a Configuration class, not a regular spring service bean or component for example.

Is that right?

不,这不完全正确。

类 提供给 @ContextConfiguration 通常 @Configuration 类,但这不是必需的。

以下是 JavaDoc for @ContextConfiguration 的摘录:

Annotated Classes

The term annotated class can refer to any of the following.

  • A class annotated with @Configuration
  • A component (i.e., a class annotated with @Component, @Service, @Repository, etc.)
  • A JSR-330 compliant class that is annotated with javax.inject annotations
  • Any other class that contains @Bean-methods

因此您可以将任何 "annotated class" 传递给 @ContextConfiguration

Or is this indeed a valid way to achieve this goal?

事实上,这是实现该目标的有效方法;但是,加载包含单个用户 bean 的 ApplicationContext 也有点不寻常。

此致,

Sam(Spring TestContext Framework 的作者)

您还可以使用 ApplicationContextRunner 使用您选择的测试配置创建上下文(如果您愿意,甚至可以使用一个 bean,但正如其他人已经提到的一个 bean在不使用任何 spring 魔法 的情况下以经典方式使用构造函数更合理)。

我喜欢这种测试方式的原因是测试 运行 非常快,因为您不需要加载所有上下文。这种方法最好在被测bean没有任何Autowired依赖时使用,否则使用@SpringBootTest.

会更方便

下面的示例说明了如何使用它来实现您的目标:

class MyTest {

  @Test
  void test_configuration_should_contains_my_bean() {

    new ApplicationContextRunner()
        .withUserConfiguration(TestConfiguration.class)
        .run(context -> {
          assertThat(context.getBean(MyTestBean.class)).isNotNull();
        });
  }

  @Configuraiton
  public static class TestConfiguration {
    @Bean
    public MyTestBean myTestBean(){
        new MyTestBean();
    }
  }
}