带有测试属性源的上下文配置
Spring框架对具有属性源层次结构的环境概念提供了第一级的支持,你可以配置带有特定于测试的属性源的集成测试。与用于@Configuration类的@PropertySource注解不同,你可以在测试类上声明@TestPropertySource注解来指定测试属性文件或内联属性的资源位置。这些测试属性源会被添加到为被标注的集成测试加载的ApplicationContext中的Environment所包含的PropertySources集合中。
你可以将 @TestPropertySource 与任何实现了 SmartContextLoader SPI 的类一起使用,但是旧的 ContextLoader SPI 实现并不支持 @TestPropertySource。
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中,并且该PropertySource具有最高的优先级。
键值对(key-value pairs)所支持的语法与Java属性文件(properties file)中定义的条目(entries)的语法是相同的:
-
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 注解中的设置。
默认属性文件检测
如果@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,那么测试类的位置(locations)或内联属性(inline properties)将分别替代由父类定义的配置。
测试配置也可以从包含类中继承。详情请参见@嵌套测试类配置。
在下一个示例中,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() {
// ...
}