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

Spring框架对环境的概念具有一流的支持,具有一组属性源的层次结构,您可以使用测试特定的属性源配置集成测试。与在@Configuration类上使用的@PropertySource注解相反,您可以在测试类上声明@TestPropertySource注解,以声明测试属性文件或内联属性的资源位置。这些测试属性源将添加到为注解集成测试加载的ApplicationContext的Environment中的PropertySources集合中。

您可以将@TestPropertySource与SmartContextLoader SPI的任何实现一起使用,但@TestPropertySource不支持旧的ContextLoader SPI的实现。

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

声明测试属性源

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

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

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

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

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

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

  • Java

  • Kotlin

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

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

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

  • key=value

  • key:value

  • key value

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

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

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

从Spring 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

  • 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...
}

继承和覆盖测试属性源

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

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

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

在下一个示例中,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() {
	// ...
}