Context Configuration with Test Property Sources

Spring Framework 一流地支持具有属性源层次结构的环境概念,并且您可以配置包含特定于测试的属性源的集成测试。与 @Configuration 类中使用的 @PropertySource 注释相反,你可以在测试类中声明 @TestPropertySource 注释,以声明测试属性文件或内联属性的资源位置。这些测试属性源被添加到为带注释的集成测试加载的 ApplicationContextEnvironment 中的 PropertySources 集中。

你可以在任何 SmartContextLoader SPI 的实现中使用 @TestPropertySource,但 @TestPropertySource 不受较旧的 ContextLoader SPI 实现支持。 SmartContextLoader 的实现能通过 MergedContextConfiguration 中的 getPropertySourceDescriptors()getPropertySourceProperties() 方法访问合并的测试属性源值。

Declaring Test Property Sources

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

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

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

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

从 Spring Framework 6.1 开始,也支持资源位置模式 — 例如,"classpath*:/config/*.properties"

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

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
	// class body...
}
1 使用一个绝对路径指定一个属性文件。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
	// class body...
}
2 使用一个绝对路径指定一个属性文件。

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

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

  • key=value

  • key:value

  • key value

虽然可以使用上面任何一种语法变体和在键和值之间的任何空格来定义属性,但建议您在测试套件中使用一种语法变体和一致的间距——例如,始终考虑使用 key = value 而不是 key= value、`key=value`等。同样,如果您使用文本块定义内联属性,则应在整个测试套件中始终使用文本块进行内联属性。 原因在于您提供的 exact 字符串将用于确定情境缓存的键。因此,要从情境缓存中受益,您必须确保一致地定义内联属性。

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

Java
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) (1)
class MyIntegrationTests {
	// class body...
}
1 通过一个字符串数组来设置两个属性。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port = 4242"]) (1)
class MyIntegrationTests {
	// class body...
}
2 通过一个字符串数组来设置两个属性。

从 Spring 框架 6.1 开始,您可以使用 文本块 在单个 String 中定义多个内联属性。以下示例使用文本块设置了两个内联属性:

Java
@ContextConfiguration
@TestPropertySource(properties = """
	timezone = GMT
	port = 4242
	""") (1)
class MyIntegrationTests {
	// class body...
}
1 通过一个文本块来设置两个属性。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["""
	timezone = GMT
	port = 4242
	"""]) (1)
class MyIntegrationTests {
	// class body...
}
2 通过一个文本块来设置两个属性。

从 Spring 框架 5.2 开始,@TestPropertySource 可用作 可重复注释 。这意味着您可以在一个测试类上多次声明 @TestPropertySource,此后 @TestPropertySource 注释中的 locationsproperties 将覆盖前一个 @TestPropertySource 注释中的 locationsproperties。 此外,您可以在测试类上声明多个组合注释,每个注释都使用 @TestPropertySource 进行元注释,所有这些 @TestPropertySource 声明都将应用于您的测试属性源。 直接呈现 @TestPropertySource 注释始终优先于元呈现 @TestPropertySource 注释。换句话说,直接呈现 @TestPropertySource 注释中的 locationsproperties 将覆盖用作元注释的 @TestPropertySource 注释中的 locationsproperties

Default Properties File Detection

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

Precedence

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

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

  • Java

  • Kotlin

@ContextConfiguration
@TestPropertySource(
	locations = "/test.properties",
	properties = {"timezone = GMT", "port = 4242"}
)
class MyIntegrationTests {
	// class body...
}
@ContextConfiguration
@TestPropertySource("/test.properties",
		properties = ["timezone = GMT", "port = 4242"]
)
class MyIntegrationTests {
	// class body...
}

Inheriting and Overriding Test Property Sources

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

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

从 Spring Framework 5.3 开始,还可以从封闭类继承测试配置。请参阅 @Nested test class configuration 了解详情。

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

  • Java

  • Kotlin

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

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

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

在下一个示例中,BaseTestApplicationContext 只能使用内联 key1 属性进行加载。相反,ExtendedTestApplicationContext 会使用内联 key1key2 属性进行加载。以下示例显示了如何使用内联属性在子类及其超类中定义属性:

  • Java

  • Kotlin

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

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
	// ...
}
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
	// ...
}

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