使用测试属性源配置上下文
Spring 框架对环境的概念提供了顶级支持,其中包含属性源的层次结构,并且你可以为集成测试配置特定于测试的属性源。与在 @Configuration
类上使用的 @PropertySource
注解不同,你可以在测试类上声明 @TestPropertySource
注解,以声明测试属性文件的资源位置或内联属性。这些测试属性源会被添加到为注解的集成测试加载的 ApplicationContext
的 Environment
中的 PropertySources
集合中。
你可以将 @TestPropertySource
与任何实现了 SmartContextLoader
SPI 的加载器一起使用,但不支持与旧版的 ContextLoader
SPI 实现一起使用。
实现了 SmartContextLoader
的加载器可以通过 MergedContextConfiguration
中的 getPropertySourceDescriptors()
和 getPropertySourceProperties()
方法访问合并后的测试属性源值。
声明测试属性源
你可以通过使用 @TestPropertySource
的 locations
或 value
属性来配置测试属性文件。
默认情况下,支持传统的和基于 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
- Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") 1
class MyIntegrationTests {
// class body...
}
使用绝对路径指定属性文件。
@ContextConfiguration
@TestPropertySource("/test.properties") 1
class MyIntegrationTests {
// class body...
}
使用绝对路径指定属性文件。
你可以通过使用 @TestPropertySource
的 properties
属性来配置键值对形式的行内属性,如下一个示例所示。所有的键值对都会以最高优先级的形式作为一个单独的测试 PropertySource
添加到封闭的 Environment
中。
支持的键值对语法与 Java 属性文件中定义的条目语法相同:
-
key=value
-
key:value
-
key value
尽管可以使用上述任何语法变体以及键和值之间的任意数量的空格来定义属性,但建议您在测试套件中使用一种语法变体并保持一致的间距——例如,考虑始终使用 key = value
而不是 key= value
、key=value
等。同样,如果您使用文本块定义内联属性,您应该在测试套件中始终使用文本块来定义内联属性。
原因是您提供的确切字符串将用于确定上下文缓存的键。因此,为了从上下文缓存中受益,您必须确保一致地定义内联属性。
以下示例设置了两个内联属性:
- Java
- Kotlin
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) 1
class MyIntegrationTests {
// class body...
}
通过字符串数组设置两个属性。
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port = 4242"]) 1
class MyIntegrationTests {
// class body...
}
通过字符串数组设置两个属性。
从 Spring Framework 6.1 开始,你可以使用文本块来在单个 String
中定义多个内联属性。以下示例使用文本块设置两个内联属性:
- Java
- Kotlin
@ContextConfiguration
@TestPropertySource(properties = """
timezone = GMT
port = 4242
""") 1
class MyIntegrationTests {
// class body...
}
通过文本块设置两个属性。
@ContextConfiguration
@TestPropertySource(properties = ["""
timezone = GMT
port = 4242
"""]) 1
class MyIntegrationTests {
// class body...
}
通过文本块设置两个属性。
@TestPropertySource
可以作为 可重复注解 使用。
这意味着你可以在单个测试类上有多个 @TestPropertySource
声明,后面的 @TestPropertySource
注解中的 locations
和 properties
会覆盖前面 @TestPropertySource
注解中的内容。
此外,你可以在测试类上声明多个组合注解,每个注解都使用 @TestPropertySource
作为元注解,所有这些 @TestPropertySource
声明都会贡献到你的测试属性源中。
直接存在的 @TestPropertySource
注解始终优先于元注解中的 @TestPropertySource
注解。换句话说,直接存在的 @TestPropertySource
注解中的 locations
和 properties
会覆盖作为元注解使用的 @TestPropertySource
注解中的 locations
和 properties
。
默认属性文件检测
如果 @TestPropertySource
被声明为一个空注解(即没有为 locations
或 properties
属性提供显式值),则会尝试检测相对于声明该注解的类的默认属性文件。例如,如果注解的测试类是 com.example.MyTest
,则相应的默认属性文件为 classpath:com/example/MyTest.properties
。如果无法检测到默认值,则会抛出 IllegalStateException
。
优先级
测试属性的优先级高于操作系统环境、Java 系统属性或通过应用程序使用 @PropertySource
声明式或编程式添加的属性源中定义的属性。因此,测试属性可用于选择性地覆盖从系统和应用程序属性源加载的属性。此外,内联属性的优先级高于从资源位置加载的属性。但请注意,通过 @DynamicPropertySource 注册的属性的优先级高于通过 @TestPropertySource
加载的属性。
在下一个示例中,timezone
和 port
属性以及 "/test.properties"
中定义的任何属性将覆盖系统和应用程序属性源中定义的相同名称的属性。此外,如果 "/test.properties"
文件定义了 timezone
和 port
属性的条目,这些条目将被使用 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
支持布尔类型的 inheritLocations
和 inheritProperties
属性,用于表示是否应继承由超类声明的属性文件资源位置和内联属性。这两个标志的默认值均为 true
。这意味着测试类会继承任何超类声明的资源位置和内联属性。具体来说,测试类的资源位置和内联属性会附加到超类声明的资源位置和内联属性之后。因此,子类可以选择扩展这些资源位置和内联属性。需要注意的是,后出现的同名属性会覆盖(即替换)先出现的同名属性。此外,上述优先级规则同样适用于继承的测试属性源。
如果 @TestPropertySource
中的 inheritLocations
或 inheritProperties
属性设置为 false
,则测试类的位置或内联属性将分别遮蔽并有效地替换由超类定义的配置。
测试配置也可以从封闭类中继承。详情请参阅 @Nested 测试类配置。
在下一个示例中,BaseTest
的 ApplicationContext
仅使用 base.properties
文件作为测试属性源进行加载。相比之下,ExtendedTest
的 ApplicationContext
则使用 base.properties
和 extended.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() {
// ...
}
在下一个示例中,BaseTest
的 ApplicationContext
仅通过内联的 key1
属性加载。相比之下,ExtendedTest
的 ApplicationContext
则是通过内联的 key1
和 key2
属性加载的。以下示例展示了如何通过内联属性在子类及其父类中定义属性:
- 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() {
// ...
}