使用测试属性源进行上下文配置

TODAY Framework 对具有属性源层次结构的环境概念提供了一流的支持,你可以使用特定于测试的属性源配置集成测试。与在 @Configuration 类上使用的 @PropertySource 注解相比,你可以在测试类上声明 @TestPropertySource 注解,以声明测试属性文件的资源位置或内联属性。这些测试属性源被添加到为带注解的集成测试加载的 ApplicationContextEnvironment 中的 PropertySources 集合中。

你将 @TestPropertySourceSmartContextLoader SPI 的任何实现一起使用,但 @TestPropertySource 不支持旧的 ContextLoader SPI 的实现。

SmartContextLoader 的实现通过 MergedContextConfiguration 中的 getPropertySourceDescriptors()getPropertySourceProperties() 方法访问合并的测试属性源值。

声明测试属性源

你可以使用 @TestPropertySourcelocationsvalue 属性来配置测试属性文件。

默认情况下,支持传统的和基于 XML 的 java.util.Properties 文件格式——例如,"classpath:/com/example/test.properties""file:///path/to/file.xml"。自 TODAY Framework 6.1 起,你可以通过 @TestPropertySource 中的 factory 属性配置自定义 PropertySourceFactory,以支持不同的文件格式,如 JSON、YAML 等。

每个路径都被解释为 Infra Resource。普通路径(例如,"test.properties")被视为相对于定义测试类的包的类路径资源。以斜杠开头的路径被视为绝对类路径资源(例如:"/org/example/test.xml")。引用 URL 的路径(例如,以 classpath:file:http: 为前缀的路径)使用指定的资源协议加载。

路径中的属性占位符(例如 ${…​})将针对 Environment 进行解析。

自 TODAY Framework 6.1 起,也支持资源位置模式——例如,"classpath*:/config/*.properties"

以下示例使用测试属性文件:

  • Java

@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
  // 类主体...
}
1 指定具有绝对路径的属性文件。

你可以使用 @TestPropertySourceproperties 属性以键值对的形式配置内联属性,如下一个示例所示。所有键值对作为具有最高优先级的单个测试 PropertySource 添加到封闭的 Environment 中。

键值对支持的语法与 Java 属性文件中条目的语法相同:

  • key=value

  • key:value

  • key value

虽然可以使用上述任何语法变体以及键和值之间任意数量的空格来定义属性,但建议你在测试套件中使用一种语法变体和一致的间距——例如,考虑始终使用 key = value 而不是 key= valuekey=value 等。同样,如果你使用文本块定义内联属性,则应在整个测试套件中一致地使用文本块作为内联属性。

原因是,你提供的确切字符串将用于确定上下文缓存的键。因此,为了从上下文缓存中受益,必须确保一致地定义内联属性。

以下示例设置了两个内联属性:

  • Java

@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) (1)
class MyIntegrationTests {
  // 类主体...
}
1 通过字符串数组设置两个属性。

自 TODAY Framework 6.1 起,你可以使用_文本块_在单个 String 中定义多个内联属性。以下示例使用文本块设置两个内联属性:

@ContextConfiguration
@TestPropertySource(properties = """
  timezone = GMT
  port = 4242
  """) (1)
class MyIntegrationTests {
  // 类主体...
}
1 通过文本块设置两个属性。

自 TODAY Framework 5.2 起,@TestPropertySource 可以用作_可重复注解_。这意味着你可以在单个测试类上拥有多个 @TestPropertySource 声明,后面的 @TestPropertySource 注解中的 locationsproperties 会覆盖前面的 @TestPropertySource 注解中的内容。

此外,你可以在测试类上声明多个组合注解,每个组合注解都元标注了 @TestPropertySource,所有这些 @TestPropertySource 声明都将贡献给你的测试属性源。

直接存在的 @TestPropertySource 注解始终优先于元存在的 @TestPropertySource 注解。换句话说,直接存在的 @TestPropertySource 注解中的 locationsproperties 将覆盖作为元注解使用的 @TestPropertySource 注解中的 locationsproperties

默认属性文件检测

如果 @TestPropertySource 被声明为空注解(即没有 locationsproperties 属性的显式值),则尝试检测相对于声明注解的类的默认属性文件。例如,如果带注解的测试类是 com.example.MyTest,则相应的默认属性文件是 classpath:com/example/MyTest.properties。如果无法检测到默认值,则抛出 IllegalStateException

优先级

测试属性的优先级高于在操作系统的环境、Java 系统属性中定义的属性,或应用程序通过使用 @PropertySource 或以编程方式添加的属性源。因此,测试属性可用于选择性地覆盖从系统和应用程序属性源加载的属性。此外,内联属性的优先级高于从资源位置加载的属性。但请注意,通过 @DynamicPropertySource 注册的属性优先级高于通过 @TestPropertySource 加载的属性。

在下一个示例中,timezoneport 属性以及在 "/test.properties" 中定义的任何属性将覆盖在系统和应用程序属性源中定义的任何同名属性。此外,如果 "/test.properties" 文件定义了 timezoneport 属性的条目,这些条目将被使用 properties 属性声明的内联属性覆盖。以下示例显示了如何同时在文件和内联中指定属性:

  • Java

@ContextConfiguration
@TestPropertySource(
  locations = "/test.properties",
  properties = {"timezone = GMT", "port = 4242"}
)
class MyIntegrationTests {
  // 类主体...
}

继承和覆盖测试属性源

@TestPropertySource 支持布尔类型的 inheritLocationsinheritProperties 属性,用于表示是否应继承超类声明的属性文件资源位置和内联属性。这两个标志的默认值均为 true。这意味着测试类继承任何超类声明的位置和内联属性。具体来说,测试类的位置和内联属性被附加到超类声明的位置和内联属性。因此,子类可以选择扩展位置和内联属性。请注意,后面出现的属性会遮蔽(即覆盖)前面出现的同名属性。此外,上述优先级规则也适用于继承的测试属性源。

如果 @TestPropertySource 中的 inheritLocationsinheritProperties 属性设置为 false,则测试类的位置或内联属性将分别遮蔽并有效地替换超类定义的配置。

自 TODAY Framework 5.3 起,测试配置也可以从封闭类继承。有关详细信息,请参阅 @Nested 测试类配置

在下一个示例中,BaseTestApplicationContext 仅使用 base.properties 文件作为测试属性源加载。相比之下,ExtendedTestApplicationContext 使用 base.propertiesextended.properties 文件作为测试属性源位置加载。以下示例显示了如何使用 properties 文件在子类及其超类中定义属性:

  • Java

@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
  // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
  // ...
}

在下一个示例中,BaseTestApplicationContext 仅使用内联 key1 属性加载。相比之下,ExtendedTestApplicationContext 使用内联 key1key2 属性加载。以下示例显示了如何使用内联属性在子类及其超类中定义属性:

  • Java

@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
  // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
  // ...
}