JUnit 5 根据激活的 Profile 进行测试

1、概览

我们经常需要为开发和部署过程中的不同阶段创建不同的配置。在 Spring Boot 应用中,我们可以为每个不同的阶段定义一个 Spring Profile 并为其创建专门的测试。

在本教程中,我们将介绍如何使用 JUnit 5 基于激活的 Spring Profile 来进行测试。

2、项目设置

首先,在我们的项目中添加 spring-boot-starter-web 依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

现在,让我们创建一个简单的 Spring Boot 应用:

@SpringBootApplication
public class ActiveProfileApplication {

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

最后,创建 application.yaml 配置文件。

3、Spring Profile

Spring Profile 提供了一种方法,通过对每个环境的特定配置进行分组,来定义和管理不同的环境。

通过激活特定的 Profile,我们可以在不同的配置之间轻松切换。

3.1、在 Properties 中激活 Profile

我们可以在 application.yaml 文件中指定要激活的 profile:

spring:
  profiles:
    active: dev

现在 Spring 将检索激活的 profile 的所有属性,并将所有专用 Bean 加载到 application context 中。

在本文中,我们将为每个 profile 创建专用的 properties 文件。

我们分别为 test 环境和 prod 环境创建两个 profile,每个 profile 都带有不同值的 profile.property.value 配置属性。

创建 application-prod.yaml 文件,并添加 profile.property.value 属性:

profile:
  property:
    value: This the the application-prod.yaml file

同样地,创建和配置 application-test.yaml

profile:
  property:
    value: This the the application-test.yaml file

最后,让我们在 application.yaml 中添加相同的属性:

profile:
  property:
    value: This the the application.yaml file

现在,让我们编写一个简单的测试,以验证应用程序在此环境中的行为是否符合预期:

@SpringBootTest(classes = ActiveProfileApplication.class)
public class DevActiveProfileUnitTest {

    @Value("${profile.property.value}")
    private String propertyString;

    @Test
    void whenDevIsActive_thenValueShouldBeKeptFromApplicationYaml() {
        Assertions.assertEquals("This the the application.yaml file", propertyString);
    }
}

变量 propertyString 注入的值来自 application.yaml 中定义的属性值。这是因为在测试执行过程中激活的 profile 是 dev,而且没有为该 profile 定义 properties 文件。

3.2、在测试类上激活 Profile

通过设置 spring.profiles.active 属性,我们可以激活相应的 profile 并加载与其相关的配置文件。

但在某些情况下,我们可能希望使用特定的 profile 执行测试类,覆盖 properties 文件中定义的 active profile。

因此,我们可以使用与 JUnit 5 兼容的注解 @ActiveProfiles,它声明了在加载测试类的 application context 时要使用的 profile。

也就是说,如果我们用 @ActiveProfiles 注解一个测试类,并将 value 属性设置为 test,那么类中的所有测试都将基于该 test profile:

@SpringBootTest(classes = ActiveProfileApplication.class)
@ActiveProfiles(value = "test")
public class TestActiveProfileUnitTest {

    @Value("${profile.property.value}")
    private String propertyString;

    @Test
    void whenTestIsActive_thenValueShouldBeKeptFromApplicationTestYaml() {
        Assertions.assertEquals("This the the application-test.yaml file", propertyString);
    }
}

value 属性是 profiles 属性的别名,是一个字符串数组(String[])。也就是说,可以指定多个激活的 profile。

例如,如果我们想指定激活的 profile 为 testprod,可以使用:

@ActiveProfiles({"prod", "test"})

这样,application context 就可以使用 testprod profile 的特定属性进行配置。

配置将按照列出的顺序应用。如果不同 profile 的配置发生冲突,则后面的会覆盖前面的。

根据不同的 profile 运行测试对于确保应用程序在不同环境中正确运行至关重要。不过,在其他环境中执行为特定 profile 设计的测试可能会带来很大风险。例如,在本地机器上运行测试时,我们可能会无意中运行为生产环境设计的测试。

为了避免这种情况,我们需要找到一种根据激活的 profile 过滤测试执行的方法。

4. @EnabledIf 注解

在 JUnit 4 中,可以使用 @IfProfileValue 注解有条件地执行测试。该注解指定了执行测试必须满足的条件。

但当我们的单元测试框架是 JUnit 5 时,我们应避免使用 @IfProfileValue,因为当前版本已不再支持它。

我们可以使用 @EnabledIf,这是一种根据条件启用或禁用方法或类的注解。

Junit 5 也提供了一个 @EnabledIf 注解。需要注意,我们应确保导入的是 Spring 提供的注解,以避免混淆。

该注解的属性如下:

  • value:条件表达式,必须为 true 才能启用测试类或单个测试。
  • expression: 条件表达式,value 的别名,注解了 @AliasFor
  • loadContext: 指定是否需要加载 context 才能判断条件。默认值为 false
  • reason:解释为何需要这一条件。

为了计算、使用在 Spring Application Context 中定义的值的条件(如激活的 profile),我们应将 boolean 属性 loadContext 设为 true

4.1、在单个激活的 Profile 下运行测试

如果只想在单个 profile 处于激活状态时运行测试类,我们可以在 value 属性中使用 SPEL 函数进行判断:

#{environment.getActiveProfiles()[0] == 'prod'}

在该函数中,environment 变量是一个实现了 Enviroment 的对象。因此 environment.getActiveProfiles() 返回当前环境中激活的 profile 数组,而 [0] 则访问该数组的第一个元素。

在测试中添加注解:

@SpringBootTest(classes = ActiveProfileApplication.class)
@EnabledIf(value = "#{environment.getActiveProfiles()[0] == 'prod'}", loadContext = true)
public class ProdActiveProfileUnitTest {

    @Value("${profile.property.value}")
    private String propertyString;

    @Test
    void whenProdIsActive_thenValueShouldBeKeptFromApplicationProdYaml() {
        Assertions.assertEquals("This the the application-prod.yaml file", propertyString);
    }

}

然后,让我们通过 @ActiveProfiles 激活 prod profile:

@SpringBootTest(classes = ActiveProfileApplication.class)
@EnabledIf(value = "#{environment.getActiveProfiles()[0] == 'prod'}", loadContext = true)
@ActiveProfiles(value = "prod")
public class ProdActiveProfileUnitTest {

    @Value("${profile.property.value}")
    private String propertyString;

    @Test
    void whenProdIsActive_thenValueShouldBeKeptFromApplicationProdYaml() {
        Assertions.assertEquals("This the the application-prod.yaml file", propertyString);
    }

}

因此,我们类中的测试将始终在 prod profile 下运行,并且仅在当前唯一激活的 profile 为 prod 时才会运行。

4.2、在多个激活的 Profile 下运行测试

如果我们想在不同的 profile 下执行测试,可以在 valueexpression 属性中使用 SPEL 函数进行判断。

#{{'test', 'prod'}.contains(environment.getActiveProfiles()[0])}

表达式解释:

  • {'test', 'prod'} 指定了一组在 Spring 应用程序中定义的两个 profile 名称。
  • .contains(environment.getActiveProfiles()[0]) 检查数组的第一个元素是否包含在前面定义的集合中。

让我们在测试类中添加 @EnableIf 注解:

@SpringBootTest(classes = ActiveProfileApplication.class)
@EnabledIf(value = "#{{'test', 'prod'}.contains(environment.getActiveProfiles()[0])}", loadContext = true)
@ActiveProfiles(value = "test")
public class MultipleActiveProfileUnitTest {
    @Value("${profile.property.value}")
    private String propertyString;

    @Autowired
    private Environment env;

    @Test
    void whenDevIsActive_thenValueShouldBeKeptFromDedicatedApplicationYaml() {
        String currentProfile = env.getActiveProfiles()[0];
        Assertions.assertEquals(String.format("This the the application-%s.yaml file", currentProfile), propertyString);
    }
}

因此,当激活的 profile 为 testprod 时,我们类中的测试将始终运行。

5、总结

在本文中,我们学习了如何使用 JUnit 5 注解根据激活的 Spring profile 执行测试、如何在测试类上启用 profile,以及如何在一个或多个特定 profile 处于激活状态时执行测试。


参考:https://www.baeldung.com/spring-boot-junit-5-testing-active-profile