测试
2. 单元测试
依赖注入应该使您的代码对容器的依赖程度低于它
与传统的 Java EE 开发一起使用。构成应用程序的 POJO 应该
可在 JUnit 或 TestNG 测试中进行测试,对象通过使用new
运算符,没有 Spring 或任何其他容器。您可以使用模拟对象(与其他有价值的测试技术结合使用)来单独测试代码。
如果您遵循 Spring 的架构建议,则生成的干净分层
代码库的组件化有助于简化单元测试。例如
您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,
在运行单元测试时无需访问持久性数据。
真正的单元测试通常运行得非常快,因为没有运行时基础设施 建立。强调真正的单元测试作为开发方法的一部分可以促进 您的生产力。您可能不需要测试章节的这一部分来帮助您编写 针对基于 IoC 的应用程序进行有效的单元测试。对于某些单元测试场景, 但是,Spring Framework 提供了模拟对象和测试支持类,这些类 本章中将对此进行描述。
2.1. 模拟对象
Spring 包括许多专门用于模拟的包:
2.1.1. 环境
这org.springframework.mock.env
包包含Environment
和PropertySource
抽象(参见 Bean 定义配置文件和PropertySource
抽象化).MockEnvironment
和MockPropertySource
对开发很有用
对依赖于特定于环境的属性的代码进行容器外测试。
2.1.2. JNDI
这org.springframework.mock.jndi
包包含 JNDI 的部分实现
SPI,您可以使用它为测试套件或独立设置简单的 JNDI 环境
应用。例如,如果 JDBCDataSource
实例绑定到同一个 JNDI
测试代码中的名称,就像在 Java EE 容器中一样,您可以重用这两个应用程序代码
以及无需修改的测试场景中的配置。
中的模拟 JNDI 支持org.springframework.mock.jndi package是
从 Spring Framework 5.2 开始正式弃用,取而代之的是从第三个开始的完整解决方案
Simple-JNDI 等方。 |
2.1.3. Servlet API
这org.springframework.mock.web
包包含一套全面的 Servlet API
模拟对象,这些对象可用于测试 Web 上下文、控制器和过滤器。这些
模拟对象针对 Spring 的 Web MVC 框架,通常更多
比动态模拟对象(如EasyMock)使用方便
或替代的 Servlet API 模拟对象(例如 MockObjects)。
从 Spring Framework 5.0 开始,中的模拟对象org.springframework.mock.web 是
基于 Servlet 4.0 API。 |
Spring MVC Test 框架基于模拟 Servlet API 对象构建,以提供 Spring MVC 的集成测试框架。参见 MockMvc。
2.1.4. Spring Web 响应式
这org.springframework.mock.http.server.reactive
包包含模拟实现
之ServerHttpRequest
和ServerHttpResponse
用于 WebFlux 应用程序。这org.springframework.mock.web.server
包包含一个模拟ServerWebExchange
那
取决于那些模拟请求和响应对象。
双MockServerHttpRequest
和MockServerHttpResponse
从同一个摘要扩展
基类作为特定于服务器的实现,并与它们共享行为。为
例如,模拟请求一旦创建就不可变,但您可以使用mutate()
方法
从ServerHttpRequest
以创建修改后的实例。
为了让模拟响应正确实现写入协定并返回
写入完成句柄(即,Mono<Void>
),它默认使用Flux
跟cache().then()
,它缓冲数据并使其可用于测试中的断言。
应用程序可以设置自定义写入函数(例如,测试无限流)。
WebTestClient 基于模拟请求和响应构建,以提供支持 在没有 HTTP 服务器的情况下测试 WebFlux 应用程序。客户端也可用于 使用正在运行的服务器进行端到端测试。
2.2. 单元测试支持类
Spring 包含许多可以帮助进行单元测试的类。他们分为两个 类别:
2.2.1. 通用测试实用程序
这org.springframework.test.util
包包含多个通用实用程序
用于单元和集成测试。
AopTestUtils
是
与 AOP 相关的实用程序方法。您可以使用这些方法获取对
隐藏在一个或多个 Spring 代理后面的底层目标对象。例如,如果您
已使用 EasyMock 或 Mockito 等库将 bean 配置为动态模拟,
并且模拟被包装在 Spring 代理中,您可能需要直接访问底层
mock 来配置对它的期望并执行验证。对于 Spring 的核心 AOP
实用程序,请参阅AopUtils
和AopProxyUtils
.
ReflectionTestUtils
是一个
基于反射的实用方法的集合。您可以在测试中使用这些方法
需要更改常量值的场景,设置非public
田
调用非public
setter 方法,或调用非public
配置或生命周期
callback 方法,用于测试以下用例的应用程序代码:
-
纵容的 ORM 框架(例如 JPA 和 Hibernate)
private
或protected
田 访问而不是public
域实体中属性的 setter 方法。 -
Spring 对注释的支持(例如
@Autowired
,@Inject
和@Resource
), 提供依赖注入private
或protected
字段、setter 方法、 和配置方法。 -
使用 Comments,例如
@PostConstruct
和@PreDestroy
用于生命周期回调 方法。
TestSocketUtils
是一个简单的
用于查找可用 TCP 端口的实用程序localhost
用于集成测试
场景。
|
2.2.2. Spring MVC 测试实用程序
这org.springframework.test.web
包装内含ModelAndViewAssert
,您可以可以与 JUnit、TestNG 或任何其他用于单元测试的测试框架结合使用处理 Spring MVCModelAndView
对象。
单元测试 Spring MVC 控制器 对 Spring MVC 进行单元测试Controller 类作为 POJO,使用ModelAndViewAssert 结合MockHttpServletRequest ,MockHttpSession ,依此类推,来自 Spring 的 Servlet API 模拟。为了对你的Spring MVC 和 REST 进行彻底的集成测试Controller 与您的WebApplicationContext Spring MVC 的配置,请改用 Spring MVC 测试框架。 |
3. 集成测试
本节(本章其余部分的大部分内容)介绍了 Spring 的集成测试 应用。 它包括以下主题:
3.1. 概述
能够执行一些集成测试而无需 部署到您的应用程序服务器或连接到其他企业基础架构。 这样做可以测试以下内容:
-
Spring IoC 容器上下文的正确连接。
-
使用 JDBC 或 ORM 工具访问数据。这可以包括正确性 的 SQL 语句、Hibernate 查询、JPA 实体映射等。
Spring Framework 为spring-test
模块。实际 JAR 文件的名称可能包括发布版本
也可能在长期内org.springframework.test
表格,取决于您从哪里获得
它来自(有关解释,请参阅依赖项管理部分)。该库包括org.springframework.test
package,其中
包含用于与 Spring 容器集成测试的有价值的类。本次测试
不依赖于应用程序服务器或其他部署环境。此类测试是
运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者
依赖于部署到应用程序服务器的远程测试。
单元和集成测试支持以注释驱动的 Spring TestContext 框架的形式提供。TestContext 框架是 与实际使用的测试框架无关,允许检测测试 在各种环境中,包括 JUnit、TestNG 等。
3.2. 集成测试的目标
Spring 的集成测试支持具有以下主要目标:
-
管理测试之间的 Spring IoC 容器缓存。
-
提供测试夹具实例的依赖注入。
-
提供适合集成测试的事务管理。
-
提供特定于 Spring 的基类,以协助 编写集成测试的开发人员。
接下来的几节介绍了每个目标,并提供了实现和 配置详细信息。
3.2.1. 上下文管理和缓存
Spring TestContext 框架提供一致的 Spring 加载ApplicationContext
instances 和WebApplicationContext
实例以及缓存
这些背景。支持缓存加载的上下文很重要,因为
启动时间可能会成为一个问题——不是因为 Spring 本身的开销,而是因为 Spring 本身的开销
因为 Spring 容器实例化的对象需要时间来实例化。为
例如,一个具有 50 到 100 个 Hibernate 映射文件的项目可能需要 10 到 20 秒才能
加载映射文件,并在每个测试中运行每个测试之前产生该成本
夹具会导致整体测试运行速度变慢,从而降低开发人员的工作效率。
测试类通常声明 XML 或 Groovy 的资源位置数组配置元数据(通常在类路径中)或组件类数组用于配置应用程序。这些位置或类与中指定的类似web.xml
或其他用于生产的配置文件 部署。
默认情况下,加载后,配置的ApplicationContext
在每次测试中重复使用。因此,每个测试套件仅产生一次设置成本,并且后续测试执行要快得多。在这种情况下,术语“测试套件”意味着所有测试都在同一个JVM 中运行 — 例如,所有测试都从给定项目的 Ant、Maven 或 Gradle 构建运行或模块。在极少数情况下,测试会损坏应用程序上下文并需要重新加载(例如,通过修改 bean 定义或应用程序的状态对象),可以将 TestContext 框架配置为重新加载配置并在执行下一个测试之前重建应用程序上下文。
3.2.2. 测试夹具的依赖注入
当 TestContext 框架加载您的应用程序上下文时,它可以选择使用依赖注入配置测试类的实例。这提供了一个使用预配置的 bean 设置测试夹具的便捷机制应用程序上下文。这里的一个强大好处是您可以重用应用程序上下文跨各种测试场景(例如,用于配置 Spring 管理的对象图形、事务代理、DataSource
实例等),从而避免了需要为单个测试用例复制复杂的测试夹具设置。
例如,考虑一个场景,我们有一个类 (HibernateTitleRepository
) 实现数据访问逻辑的Title
域实体。我们想写
测试以下领域的集成测试:
-
Spring 配置:基本上,是与
HibernateTitleRepository
豆正确且存在? -
Hibernate 映射文件配置:是否所有内容都正确映射,并且是 正确延迟加载设置?
-
的逻辑
HibernateTitleRepository
:该类的配置实例 按预期执行?
请参阅使用 TestContext 框架注入测试夹具的依赖项。
3.2.3. 事务管理
在访问真实数据库的测试中,一个常见问题是它们对 持久性存储。即使使用开发数据库,对状态的更改也可能 影响未来的测试。此外,许多作(例如插入或修改持久性 数据 — 不能在交易之外执行(或验证)。
TestContext 框架解决了这个问题。默认情况下,框架会创建
回滚每个测试的事务。您可以编写可以假设存在的代码
交易的。如果您在测试中调用事务代理对象,它们的行为
正确地,根据他们配置的事务语义。此外,如果测试
方法在事务中运行时删除所选表的内容
托管,事务默认回滚,数据库返回到
执行测试之前的状态。事务支持由以下方式为测试提供
使用PlatformTransactionManager
在测试的应用程序上下文中定义的 bean。
如果您希望提交一个事务(不寻常,但当您想要
特定测试来填充或修改数据库),您可以告诉 TestContext
框架,使用@Commit
注解。
请参阅使用 TestContext 框架进行事务管理。
3.2.4. 集成测试的支持类
Spring TestContext 框架提供了几个abstract
支持类
简化集成测试的编写。这些基本测试类提供定义明确的
钩子到测试框架以及方便的实例变量和方法,
这使您可以访问:
-
这
ApplicationContext
,用于执行显式 Bean 查找或测试 整个背景。 -
一个
JdbcTemplate
,用于执行SQL语句查询数据库。您可以使用这样的 在执行与数据库相关的之前和之后确认数据库状态的查询 应用程序代码,并且 Spring 确保此类查询在相同的范围内运行 transaction 作为应用程序代码。与 ORM 工具结合使用时,请确保 以避免误报。
此外,您可能希望使用 实例变量和特定于项目的方法。
请参阅 TestContext 框架的支持类。
3.3. JDBC 测试支持
这org.springframework.test.jdbc
包装内含JdbcTestUtils
,这是一个
JDBC 相关实用函数的集合,旨在简化标准数据库
测试场景。具体说来JdbcTestUtils
提供以下静态实用程序
方法。
-
countRowsInTable(..)
:计算给定表中的行数。 -
countRowsInTableWhere(..)
:使用 提供WHERE
第。 -
deleteFromTables(..)
:从指定表中删除所有行。 -
deleteFromTableWhere(..)
:使用提供的WHERE
第。 -
dropTables(..)
:删除指定的表。
这 |
3.4. 注释
本节介绍了在测试 Spring 应用程序时可以使用的注释。 它包括以下主题:
3.4.1. Spring 测试注释
Spring 框架提供了以下一组特定于 Spring 的注释,您可以 可以在单元测试和集成测试中与 TestContext 框架结合使用。 有关更多信息,请参阅相应的 javadoc,包括 default 属性 值、属性别名和其他详细信息。
Spring 的测试注解包括以下内容:
@BootstrapWith
@BootstrapWith
是一个类级注解,可用于配置 Spring
TestContext 框架是引导的。具体来说,您可以使用@BootstrapWith
自
指定自定义TestContextBootstrapper
.有关更多详细信息,请参阅有关引导 TestContext 框架的部分。
@ContextConfiguration
@ContextConfiguration
定义用于确定如何加载并配置ApplicationContext
用于集成测试。 具体说来@ContextConfiguration
声明应用程序上下文资源locations
或 元件classes
用于加载上下文。
资源位置通常是位于类路径中的 XML 配置文件或 Groovy 脚本,而组件类通常是@Configuration
类。 然而 资源位置也可以引用文件系统中的文件和脚本,组件类可以是@Component
类@Service
类,依此类推。有关更多详细信息,请参阅组件类。
以下示例显示了@ContextConfiguration
引用 XML 的注释 文件:
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
// class body...
}
1 | 引用 XML 文件。 |
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
// class body...
}
1 | 引用 XML 文件。 |
以下示例显示了@ContextConfiguration
引用类的注释:
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
// class body...
}
1 | 指一个类。 |
@ContextConfiguration(classes = [TestConfig::class]) (1)
class ConfigClassApplicationContextTests {
// class body...
}
1 | 指一个类。 |
作为替代方案,或者除了声明资源位置或组件类之外,您可以使用@ContextConfiguration
声明ApplicationContextInitializer
类。 以下示例显示了这种情况:
@ContextConfiguration(initializers = CustomContextInitializer.class) (1)
class ContextInitializerTests {
// class body...
}
1 | 声明初始值设定项类。 |
@ContextConfiguration(initializers = [CustomContextInitializer::class]) (1)
class ContextInitializerTests {
// class body...
}
1 | 声明初始值设定项类。 |
您可以选择使用@ContextConfiguration
声明ContextLoader
策略作为
井。但是请注意,您通常不需要显式配置加载器,
由于默认加载器支持initializers
和任一资源locations
或
元件classes
.
以下示例同时使用位置和加载器:
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
class CustomLoaderXmlApplicationContextTests {
// class body...
}
1 | 配置位置和自定义加载器。 |
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) (1)
class CustomLoaderXmlApplicationContextTests {
// class body...
}
1 | 配置位置和自定义加载器。 |
@ContextConfiguration 提供对继承资源位置的支持或
配置类以及由超类声明的上下文初始值设定项
或封闭类。 |
请参阅上下文管理,@Nested
测试类配置和@ContextConfiguration
javadocs 了解更多详情。
@WebAppConfiguration
@WebAppConfiguration
是一个类级注解,可用于声明ApplicationContext
loaded for an integration test 应该是WebApplicationContext
.
仅仅是@WebAppConfiguration
在测试类上确保WebApplicationContext
加载以进行测试,使用默认值"file:src/main/webapp"
对于 Web 应用程序根目录的路径(即
资源基础路径)。资源基础路径在后台用于创建MockServletContext
,它充当ServletContext
对于测试的WebApplicationContext
.
以下示例演示如何使用@WebAppConfiguration
注解:
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
1 | 这@WebAppConfiguration 注解。 |
要覆盖默认值,您可以使用
含蓄value
属性。双classpath:
和file:
资源前缀是
支持。如果未提供资源前缀,则假定路径为文件系统
资源。以下示例显示了如何指定类路径资源:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
// class body...
}
1 | 指定类路径资源。 |
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
// class body...
}
1 | 指定类路径资源。 |
请注意@WebAppConfiguration
必须与@ContextConfiguration
,无论是在单个测试类中还是在测试类中
等级制度。请参阅@WebAppConfiguration
javadoc 了解更多详情。
@ContextHierarchy
@ContextHierarchy
是一个类级注释,用于定义ApplicationContext
用于集成测试的实例。@ContextHierarchy
应该是
使用一个或多个列表声明@ContextConfiguration
实例,每个实例
定义上下文层次结构中的级别。以下示例演示了如何使用@ContextHierarchy
在单个测试类 (@ContextHierarchy
也可以用
在测试类层次结构中):
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}
@ContextHierarchy(
ContextConfiguration("/parent-config.xml"),
ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [AppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
// class body...
}
如果您需要合并或覆盖给定级别的上下文层次结构,则必须通过提供相同的值来显式命名该级别name
属性@ContextConfiguration
在每个相应的级别。请参阅上下文层次结构和@ContextHierarchy
javadoc更多示例。
@ActiveProfiles
@ActiveProfiles
是一个类级注释,用于声明哪个 bean定义配置文件在加载ApplicationContext
对于集成测试。
以下示例表示dev
配置文件应处于活动状态:
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | 指示dev 配置文件应处于活动状态。 |
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | 指示dev 配置文件应处于活动状态。 |
以下示例表示dev
和integration
配置文件应
活跃:
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | 指示dev 和integration 配置文件应处于活动状态。 |
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | 指示dev 和integration 配置文件应处于活动状态。 |
@ActiveProfiles 支持继承活动 Bean 定义概要文件
默认情况下由超类和封闭类声明。您还可以解决活动
通过实现自定义ActiveProfilesResolver 并使用resolver 属性@ActiveProfiles . |
请参阅使用环境配置文件进行上下文配置,@Nested
测试类配置和@ActiveProfiles
javadoc 的
示例和更多详细信息。
@TestPropertySource
@TestPropertySource
是一个类级注解,可用于配置
要添加到PropertySources
在Environment
对于ApplicationContext
加载
集成测试。
以下示例演示了如何从类路径声明属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 从test.properties 在类路径的根目录中。 |
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 从test.properties 在类路径的根目录中。 |
以下示例演示如何声明内联属性:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
// class body...
}
1 | 宣timezone 和port 性能。 |
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
// class body...
}
1 | 宣timezone 和port 性能。 |
有关示例和更多详细信息,请参阅使用测试属性源进行上下文配置。
@DynamicPropertySource
@DynamicPropertySource
是一个方法级注解,可用于注册要添加到PropertySources
在Environment
为
一ApplicationContext
加载以进行集成测试。动态属性很有用
当您预先不知道属性的值时,例如,如果属性
由外部资源管理,例如由 Testcontainers 项目管理的容器。
以下示例演示了如何注册动态属性:
@ContextConfiguration
class MyIntegrationTests {
static MyExternalServer server = // ...
@DynamicPropertySource (1)
static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
registry.add("server.port", server::getPort); (3)
}
// tests ...
}
1 | 注释static 方法@DynamicPropertySource . |
2 | 接受DynamicPropertyRegistry 作为论据。 |
3 | 注册动态server.port 属性,以便从服务器延迟检索。 |
@ContextConfiguration
class MyIntegrationTests {
companion object {
@JvmStatic
val server: MyExternalServer = // ...
@DynamicPropertySource (1)
@JvmStatic
fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
registry.add("server.port", server::getPort) (3)
}
}
// tests ...
}
1 | 注释static 方法@DynamicPropertySource . |
2 | 接受DynamicPropertyRegistry 作为论据。 |
3 | 注册动态server.port 属性,以便从服务器延迟检索。 |
有关更多详细信息,请参阅使用动态属性源进行上下文配置。
@DirtiesContext
@DirtiesContext
表示底层 SpringApplicationContext
已经
在执行测试期间被弄脏(即测试在
某种方式——例如,通过更改单例 Bean 的状态)并且应该是
闭。当应用程序上下文被标记为脏时,它将从测试中删除
框架的缓存并关闭。因此,底层 Spring 容器是
为需要具有相同配置的上下文的任何后续测试重新构建
元数据。
您可以使用@DirtiesContext
作为类级和方法级注释
相同的类或类层次结构。在这种情况下,ApplicationContext
标记为
在任何此类注释方法之前或之后以及电流之前或之后都为脏
test 类,具体取决于配置的methodMode
和classMode
.
以下示例解释了上下文何时会因各种 配置场景:
-
在当前测试类之前,当在类模式设置为
BEFORE_CLASS
.Java@DirtiesContext(classMode = BEFORE_CLASS) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在当前测试类之前弄脏上下文。 Kotlin@DirtiesContext(classMode = BEFORE_CLASS) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在当前测试类之前弄脏上下文。 -
在当前测试类之后,当在类模式设置为
AFTER_CLASS
(即默认类模式)。Java@DirtiesContext (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在当前测试类之后弄脏上下文。 Kotlin@DirtiesContext (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在当前测试类之后弄脏上下文。 -
在当前测试类中的每个测试方法之前,当在具有 class 的类上声明时 mode 设置为
BEFORE_EACH_TEST_METHOD.
Java@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在每个测试方法之前弄脏上下文。 Kotlin@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在每个测试方法之前弄脏上下文。 -
在当前测试类中的每个测试方法之后,当在具有 class 的类上声明时 mode 设置为
AFTER_EACH_TEST_METHOD.
Java@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在每个测试方法之后弄脏上下文。 Kotlin@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在每个测试方法之后弄脏上下文。 -
在当前测试之前,当在方法模式设置为
BEFORE_METHOD
.Java@DirtiesContext(methodMode = BEFORE_METHOD) (1) @Test void testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
1 在当前测试方法之前弄脏上下文。 Kotlin@DirtiesContext(methodMode = BEFORE_METHOD) (1) @Test fun testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
1 在当前测试方法之前弄脏上下文。 -
在当前测试之后,当在方法模式设置为
AFTER_METHOD
(即默认方法模式)。Java@DirtiesContext (1) @Test void testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
1 在当前测试方法之后弄脏上下文。 Kotlin@DirtiesContext (1) @Test fun testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
1 在当前测试方法之后弄脏上下文。
如果您使用@DirtiesContext
在上下文配置为上下文一部分的测试中
hierarchy 与@ContextHierarchy
,您可以使用hierarchyMode
标志来控制方式
上下文缓存被清除。默认情况下,使用详尽的算法来清除
上下文缓存,不仅包括当前级别,还包括所有其他上下文
共享当前测试通用的祖先上下文的层次结构。都ApplicationContext
驻留在共同祖先的子层次结构中的实例
上下文从上下文缓存中删除并关闭。如果穷举算法是
overkill 对于特定用例,您可以指定更简单的当前级别算法,
如以下示例所示。
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}
class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
void test() {
// some logic that results in the child context being dirtied
}
}
1 | 使用当前级别算法。 |
@ContextHierarchy(
ContextConfiguration("/parent-config.xml"),
ContextConfiguration("/child-config.xml"))
open class BaseTests {
// class body...
}
class ExtendedTests : BaseTests() {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
fun test() {
// some logic that results in the child context being dirtied
}
}
1 | 使用当前级别算法。 |
有关EXHAUSTIVE
和CURRENT_LEVEL
算法,请参阅DirtiesContext.HierarchyMode
javadoc 的文档。
@TestExecutionListeners
@TestExecutionListeners
用于注册特定测试类的侦听器,其
子类及其嵌套类。如果您希望在全球范围内注册监听器,您可以
应通过中描述的自动发现机制进行注册TestExecutionListener
配置.
以下示例演示如何注册两个TestExecutionListener
实现:
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 寄存器二TestExecutionListener 实现。 |
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 寄存器二TestExecutionListener 实现。 |
默认情况下,@TestExecutionListeners
支持继承
超类或封闭类。看@Nested
测试类配置和@TestExecutionListeners
Java文档有关示例和更多详细信息。如果您发现需要切换
回到使用默认值TestExecutionListener
实现,请参阅注释
在注册TestExecutionListener
实现.
@RecordApplicationEvents
@RecordApplicationEvents
是一个类级注释,用于指示 Spring TestContext Framework 记录在ApplicationContext
在执行单个测试期间。
可以通过ApplicationEvents
测试中的 API。
请参阅应用程序事件和@RecordApplicationEvents
Java文档有关示例和更多详细信息。
@Commit
@Commit
指示事务测试方法的事务应为
在测试方法完成后提交。您可以使用@Commit
作为直接
替换@Rollback(false)
更明确地传达代码的意图。
类似于@Rollback
,@Commit
也可以声明为类级或方法级
注解。
以下示例演示如何使用@Commit
注解:
@Commit (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 将测试结果提交到数据库。 |
@Commit (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 将测试结果提交到数据库。 |
@Rollback
@Rollback
指示事务测试方法的事务是否应为
测试方法完成后回滚。如果true
,则交易滚动
返回。否则,事务将被提交(另请参阅@Commit
).在 Spring 中回滚进行集成测试
TestContext 框架默认为true
便@Rollback
未显式声明。
当声明为类级注解时,@Rollback
定义默认回滚
测试类层次结构中所有测试方法的语义。当声明为
方法级注解,@Rollback
定义特定测试的回滚语义
方法,可能会覆盖类级@Rollback
或@Commit
语义学。
以下示例会导致测试方法的结果不会回滚(即 result 提交到数据库):
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 不要回滚结果。 |
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 不要回滚结果。 |
@BeforeTransaction
@BeforeTransaction
表示带注释的void
方法应在
transaction 已启动,对于已配置为在
使用 Spring 的@Transactional
注解。@BeforeTransaction
方法
不需要public
并且可以在基于 Java 8 的接口默认值上声明
方法。
以下示例演示如何使用@BeforeTransaction
注解:
@BeforeTransaction (1)
void beforeTransaction() {
// logic to be run before a transaction is started
}
1 | 在事务之前运行此方法。 |
@BeforeTransaction (1)
fun beforeTransaction() {
// logic to be run before a transaction is started
}
1 | 在事务之前运行此方法。 |
@AfterTransaction
@AfterTransaction
表示带注释的void
方法应该在
transaction 已结束,对于已配置为在
使用 Spring 的@Transactional
注解。@AfterTransaction
方法
不需要public
并且可以在基于 Java 8 的接口默认值上声明
方法。
@AfterTransaction (1)
void afterTransaction() {
// logic to be run after a transaction has ended
}
1 | 在事务后运行此方法。 |
@AfterTransaction (1)
fun afterTransaction() {
// logic to be run after a transaction has ended
}
1 | 在事务后运行此方法。 |
@Sql
@Sql
用于注释测试类或测试方法,以配置要运行的 SQL 脚本
在集成测试期间针对给定数据库。以下示例演示如何使用
它:
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
void userTest() {
// run code that relies on the test schema and test data
}
1 | 为此测试运行两个脚本。 |
@Test
@Sql("/test-schema.sql", "/test-user-data.sql") (1)
fun userTest() {
// run code that relies on the test schema and test data
}
1 | 为此测试运行两个脚本。 |
有关更多详细信息,请参阅使用 @Sql 以声明方式执行 SQL 脚本。
@SqlConfig
@SqlConfig
定义用于确定如何解析和运行 SQL 脚本的元数据
配置为@Sql
注解。以下示例演示如何使用它:
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
void userTest() {
// run code that relies on the test data
}
1 | 在 SQL 脚本中设置注释前缀和分隔符。 |
@Test
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) (1)
fun userTest() {
// run code that relies on the test data
}
1 | 在 SQL 脚本中设置注释前缀和分隔符。 |
@SqlMergeMode
@SqlMergeMode
用于注释测试类或测试方法,以配置是否
方法级@Sql
声明与类级合并@Sql
声明。如果@SqlMergeMode
未在测试类或测试方法上声明,则OVERRIDE
合并模式
将默认使用。使用OVERRIDE
模式, 方法级@Sql
声明将
有效覆盖类级@Sql
声明。
请注意,方法级@SqlMergeMode
声明覆盖类级声明。
以下示例演示如何使用@SqlMergeMode
在班级层面。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将@Sql merge mode 设置为MERGE 对于类中的所有测试方法。 |
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
fun standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将@Sql merge mode 设置为MERGE 对于类中的所有测试方法。 |
以下示例演示如何使用@SqlMergeMode
在方法层面。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) (1)
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将@Sql merge mode 设置为MERGE 对于特定的测试方法。 |
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) (1)
fun standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将@Sql merge mode 设置为MERGE 对于特定的测试方法。 |
@SqlGroup
@SqlGroup
是一个容器注释,聚合了多个@Sql
附注。您可以
用@SqlGroup
原生声明多个嵌套的@Sql
注释,或者您可以使用它
结合 Java 8 对可重复注释的支持,其中@Sql
可以
在同一类或方法上声明多次,隐式生成此容器
注解。以下示例演示如何声明 SQL 组:
@Test
@SqlGroup({ (1)
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
1 | 声明一组 SQL 脚本。 |
@Test
@SqlGroup( (1)
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql"))
fun userTest() {
// run code that uses the test schema and test data
}
1 | 声明一组 SQL 脚本。 |
3.4.2. 标准注释支持
以下注释受标准语义支持,适用于 Spring TestContext 框架。请注意,这些注释并非特定于测试 并且可以在 Spring Framework 中的任何位置使用。
-
@Autowired
-
@Qualifier
-
@Value
-
@Resource
(javax.annotation) 如果存在 JSR-250 -
@ManagedBean
(javax.annotation) 如果存在 JSR-250 -
@Inject
(javax.inject) 如果存在 JSR-330 -
@Named
(javax.inject) 如果存在 JSR-330 -
@PersistenceContext
(javax.persistence) 如果 JPA 存在 -
@PersistenceUnit
(javax.persistence) 如果 JPA 存在 -
@Required
-
@Transactional
(org.springframework.transaction.annotation) 具有有限的属性支持。
JSR-250 生命周期注释
在 Spring TestContext 框架中,您可以使用 如果测试类中的方法被 |
3.4.3. Spring JUnit 4 测试注释
仅当与 SpringRunner、Spring 的 JUnit 4规则或 Spring 的 JUnit 4 支持类结合使用时,才支持以下注解:
@IfProfileValue
@IfProfileValue
表示已为特定测试启用带注释的测试
环境。如果配置的ProfileValueSource
返回匹配项value
对于
提供name
,则启用测试。否则,测试将被禁用,并且实际上,
忽视。
您可以申请@IfProfileValue
在类级别、方法级别或两者兼而有之。
类级用法@IfProfileValue
优先于任何方法级用法
该类或其子类中的方法。具体而言,如果测试是
在类级别和方法级别启用。没有@IfProfileValue
表示隐式启用测试。这类似于 JUnit 4 的语义@Ignore
注释,但@Ignore
始终禁用测试。
以下示例显示了一个测试,该测试具有@IfProfileValue
注解:
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
1 | 仅当 Java 提供商为“Oracle Corporation”时才运行此测试。 |
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
fun testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
1 | 仅当 Java 提供商为“Oracle Corporation”时才运行此测试。 |
或者,您可以配置@IfProfileValue
带有values
(与OR
语义)来实现对 JUnit 4 环境中测试组的类似 TestNG 的支持。
请考虑以下示例:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
1 | 为单元测试和集成测试运行此测试。 |
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) (1)
@Test
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
1 | 为单元测试和集成测试运行此测试。 |
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration
是类级注解,指定类型
之ProfileValueSource
检索通过@IfProfileValue
注解。如果@ProfileValueSourceConfiguration
未声明为
测试SystemProfileValueSource
默认使用。以下示例演示如何
用@ProfileValueSourceConfiguration
:
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
// class body...
}
1 | 使用自定义配置文件值源。 |
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) (1)
class CustomProfileValueSourceTests {
// class body...
}
1 | 使用自定义配置文件值源。 |
@Timed
@Timed
表示带注释的测试方法必须在指定的
时间段(以毫秒为单位)。如果文本执行时间超过指定时间
期间,测试失败。
该时间段包括运行测试方法本身、测试的任何重复(请参阅@Repeat
),以及测试夹具的任何设置或拆卸。以下内容
示例显示如何使用它:
@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to run
}
1 | 将测试的时间段设置为一秒。 |
@Timed(millis = 1000) (1)
fun testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to run
}
1 | 将测试的时间段设置为一秒。 |
Spring的@Timed
注释的语义与 JUnit 4 的语义不同@Test(timeout=…)
支持。具体来说,由于 JUnit 4 处理测试执行超时的方式
(即,通过在单独的Thread
),@Test(timeout=…)
如果测试时间过长,则先发制人地使测试失败。Spring的@Timed
,另一方面
手,不会先发制人地通过测试,而是等待测试完成
在失败之前。
@Repeat
@Repeat
表示必须重复运行带注释的测试方法。的次数在注释中指定了要运行测试方法的次数。
要重复的执行范围包括测试方法本身的执行,以及以及测试夹具的任何设置或拆卸。当与SpringMethodRule
,范围还包括通过以下方式准备测试实例TestExecutionListener
实现。 这 以下示例演示如何使用@Repeat
注解:
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
// ...
}
1 | 重复此测试十次。 |
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
// ...
}
1 | 重复此测试十次。 |
3.4.4. Spring JUnit Jupiter 测试注释
当与SpringExtension
和 JUnit Jupiter(即 JUnit 5 中的编程模型):
@SpringJUnitConfig
@SpringJUnitConfig
是一个组合的注释,它结合了@ExtendWith(SpringExtension.class)
来自 JUnit Jupiter 与@ContextConfiguration
从 Spring TestContext 框架。它可以在类级别用作插入式替换@ContextConfiguration
. 关于配置选项,唯一的 区别@ContextConfiguration
和@SpringJUnitConfig
是该组件类可以用value
属性@SpringJUnitConfig
.
以下示例演示如何使用@SpringJUnitConfig
注释来指定
配置类:
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置类。 |
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置类。 |
以下示例演示如何使用@SpringJUnitConfig
注释来指定配置文件的位置:
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置文件的位置。 |
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置文件的位置。 |
请参阅上下文管理以及 javadoc@SpringJUnitConfig
和@ContextConfiguration
了解更多详情。
@SpringJUnitWebConfig
@SpringJUnitWebConfig
是一个组合的注释,它结合了@ExtendWith(SpringExtension.class)
来自 JUnit Jupiter 与@ContextConfiguration
和@WebAppConfiguration
来自 Spring TestContext 框架。您可以在课堂上使用它
水平作为直接替代品@ContextConfiguration
和@WebAppConfiguration
.
关于配置选项,唯一的区别@ContextConfiguration
和@SpringJUnitWebConfig
是您可以使用value
属性@SpringJUnitWebConfig
.此外,您可以覆盖value
属性从@WebAppConfiguration
仅通过使用resourcePath
属性@SpringJUnitWebConfig
.
以下示例演示如何使用@SpringJUnitWebConfig
注解来指定
配置类:
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置类。 |
@SpringJUnitWebConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置类。 |
以下示例演示如何使用@SpringJUnitWebConfig
注释来指定配置文件的位置:
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置文件的位置。 |
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置文件的位置。 |
请参阅上下文管理以及 javadoc@SpringJUnitWebConfig
,@ContextConfiguration
和@WebAppConfiguration
了解更多详情。
@TestConstructor
@TestConstructor
是类型级注释,用于配置参数
的测试类构造函数从测试的ApplicationContext
.
如果@TestConstructor
在测试类上不存在或元存在,默认测试
将使用构造函数自动连线模式。有关如何更改的详细信息,请参阅下面的提示
默认模式。但请注意,本地声明@Autowired
在
构造函数优先于两者@TestConstructor
和默认模式。
更改默认测试构造函数自动连线模式
默认的测试构造函数自动配线模式可以通过设置 从 Spring Framework 5.3 开始,默认模式也可以配置为 JUnit Platform 配置参数。 如果 |
从 Spring Framework 5.2 开始,@TestConstructor 仅结合支持
使用SpringExtension 与 JUnit Jupiter 一起使用。请注意,SpringExtension 是
通常会自动为您注册 - 例如,在使用@SpringJUnitConfig 和@SpringJUnitWebConfig 或来自
Spring Boot 测试。 |
@NestedTestConfiguration
@NestedTestConfiguration
是类型级注解,用于配置
Spring 测试配置注释在封闭的类层次结构中处理
用于内部测试类。
如果@NestedTestConfiguration
在测试类中不存在或元存在,在其
超类型层次结构,或者在其封闭类层次结构中,默认封闭
将使用配置继承模式。有关如何作的详细信息,请参阅下面的提示
更改默认模式。
更改缺省封闭配置继承模式
默认的封闭配置继承模式是 |
Spring TestContext 框架尊重@NestedTestConfiguration
语义
后面的注释。
使用@NestedTestConfiguration 通常只有结合起来才有意义
跟@Nested JUnit Jupiter 中的测试类;但是,可能还有其他测试
支持 Spring 的框架和利用此功能的嵌套测试类
注解。 |
看@Nested
测试类配置举个例子,进一步
详。
@EnabledIf
@EnabledIf
用于表示带注释的 JUnit Jupiter 测试类或测试方法
已启用,如果提供的expression
评估为true
.
具体而言,如果表达式的计算结果为Boolean.TRUE
或String
等于true
(忽略大小写),则测试已启用。当应用于类级别时,所有测试方法
默认情况下也会自动启用该类。
表达式可以是以下任一表达式:
-
Spring 表达式语言 (SpEL) 表达式。例如:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
春季可用属性的占位符
Environment
. 例如:@EnabledIf("${smoke.tests.enabled}")
-
文本文字。例如:
@EnabledIf("true")
但是请注意,文本文字不是动态解析的结果
属性占位符的实用价值为零,因为@EnabledIf("false")
是
相当于@Disabled
和@EnabledIf("true")
在逻辑上毫无意义。
您可以使用@EnabledIf
作为元注释来创建自定义组合注释。 为 示例,您可以创建自定义@EnabledOnMac
注释如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
annotation class EnabledOnMac {}
|
从 JUnit 5.7 开始,JUnit Jupiter 还有一个名为 |
@DisabledIf
@DisabledIf
用于表示带注释的 JUnit Jupiter 测试类或测试方法被禁用,如果提供的expression
评估为true
. 具体而言,如果表达式的计算结果为Boolean.TRUE
或String
平等 自true
(忽略大小写),则测试被禁用。当在类级别应用时,该类中的所有test 方法也会自动禁用。
表达式可以是以下任一表达式:
-
Spring 表达式语言 (SpEL) 表达式。例如:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
春季可用属性的占位符
Environment
. 例如:@DisabledIf("${smoke.tests.disabled}")
-
文本文字。例如:
@DisabledIf("true")
但是请注意,文本文字不是动态解析的结果
属性占位符的实用价值为零,因为@DisabledIf("true")
是
相当于@Disabled
和@DisabledIf("false")
在逻辑上毫无意义。
您可以使用@DisabledIf
作为元注释来创建自定义组合注释。 为 示例,您可以创建自定义@DisabledOnMac
注释如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
annotation class DisabledOnMac {}
|
从 JUnit 5.7 开始,JUnit Jupiter 还有一个名为 |
3.4.5. 测试的元注释支持
您可以使用大多数与测试相关的注释作为元注释来创建自定义组合 注释并减少跨测试套件的配置重复。
您可以将以下每一项用作元注释,并结合 TestContext 框架。
-
@BootstrapWith
-
@ContextConfiguration
-
@ContextHierarchy
-
@ActiveProfiles
-
@TestPropertySource
-
@DirtiesContext
-
@WebAppConfiguration
-
@TestExecutionListeners
-
@Transactional
-
@BeforeTransaction
-
@AfterTransaction
-
@Commit
-
@Rollback
-
@Sql
-
@SqlConfig
-
@SqlMergeMode
-
@SqlGroup
-
@Repeat
(仅在 JUnit 4 上受支持) -
@Timed
(仅在 JUnit 4 上受支持) -
@IfProfileValue
(仅在 JUnit 4 上受支持) -
@ProfileValueSourceConfiguration
(仅在 JUnit 4 上受支持) -
@SpringJUnitConfig
(仅在 JUnit Jupiter 上受支持) -
@SpringJUnitWebConfig
(仅在 JUnit Jupiter 上受支持) -
@TestConstructor
(仅在 JUnit Jupiter 上受支持) -
@NestedTestConfiguration
(仅在 JUnit Jupiter 上受支持) -
@EnabledIf
(仅在 JUnit Jupiter 上受支持) -
@DisabledIf
(仅在 JUnit Jupiter 上受支持)
请考虑以下示例:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
如果我们发现我们在基于 JUnit 4 的 JUnit 4 中重复前面的配置 测试套件,我们可以通过引入自定义组合注释来减少重复 集中了 Spring 的通用测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
然后我们可以使用我们的自定义@TransactionalDevTestConfig
注释来简化
配置单个基于 JUnit 4 的测试类,如下所示:
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests
如果我们编写使用 JUnit Jupiter 的测试,我们可以进一步减少代码重复, 因为 JUnit 5 中的注释也可以用作元注释。考虑以下几点 例:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
如果我们发现我们在 JUnit 上重复前面的配置 基于 Jupiter 的测试套件,我们可以通过引入自定义组合来减少重复 集中 Spring 和 JUnit Jupiter 的通用测试配置的注释, 如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
然后我们可以使用我们的自定义@TransactionalDevTestConfig
注释来简化
基于 JUnit Jupiter 的各个测试类的配置,如下所示:
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
由于 JUnit Jupiter 支持@Test
,@RepeatedTest
,ParameterizedTest
,
和其他作为元注释,您还可以在
测试方法级别。例如,如果我们希望创建一个组合注释,该注释将
这@Test
和@Tag
来自 JUnit Jupiter 的注释,其中包含@Transactional
注释,我们可以创建一个@TransactionalIntegrationTest
注释,作为
遵循:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }
然后我们可以使用我们的自定义@TransactionalIntegrationTest
注释来简化
配置基于 JUnit Jupiter 的各个测试方法,如下所示:
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
@TransactionalIntegrationTest
fun saveOrder() { }
@TransactionalIntegrationTest
fun deleteOrder() { }
有关更多详细信息,请参阅 Spring Annotation 编程模型 wiki 页面。
3.5. Spring TestContext 框架
Spring TestContext 框架(位于org.springframework.test.context
package)提供通用的、注释驱动的单元和集成测试支持,即
与正在使用的测试框架无关。TestContext 框架还放置了一个很棒的
处理对约定比配置的重要性,合理的默认值是
可以通过基于注释的配置覆盖。
除了通用测试基础设施外,TestContext 框架还提供
显式支持 JUnit 4、JUnit Jupiter(又名 JUnit 5)和 TestNG。对于 JUnit 4 和
TestNG,Spring 提供了abstract
支持课程。此外,Spring 还提供了自定义
JUnitRunner
和自定义 JUnitRules
对于 JUnit 4 和自定义Extension
对于 JUnit
Jupiter 可以让你编写所谓的 POJO 测试类。POJO 测试类不是
扩展特定类层次结构所需的,例如abstract
支持课程。
以下部分概述了 TestContext 框架的内部结构。 如果您只对使用该框架感兴趣,而对扩展它不感兴趣 使用您自己的自定义监听器或自定义加载器,请随时直接转到 配置(上下文管理、依赖注入、事务 management)、支持类和注释支持部分。
3.5.1. 关键抽象
该框架的核心包括TestContextManager
class 和TestContext
,TestExecutionListener
和SmartContextLoader
接口。一个TestContextManager
为每个测试类创建(例如,用于执行
JUnit Jupiter 中单个测试类中的所有测试方法)。这TestContextManager
,
反过来,管理一个TestContext
它包含当前测试的上下文。这TestContextManager
还会更新TestContext
随着测试的进行
并委托TestExecutionListener
实现,它检测实际的
通过提供依赖注入、管理事务等来测试执行。一个SmartContextLoader
负责加载ApplicationContext
对于给定的测试
类。请参阅 javadoc 和
Spring 测试套件,以获取各种实现的更多信息和示例。
TestContext
TestContext
封装运行测试的上下文(与
实际使用的测试框架),并为
它负责的测试实例。这TestContext
也委托给SmartContextLoader
加载ApplicationContext
如果需要。
TestContextManager
TestContextManager
是 Spring TestContext 框架的主要入口点,并且是
负责管理单个TestContext
并向每个注册的事件发出信号TestExecutionListener
在定义明确的测试执行点:
-
在特定测试框架的任何“before class”或“before all”方法之前。
-
测试实例后处理。
-
在特定测试框架的任何“之前”或“之前”方法之前。
-
在执行测试方法之前,但在测试设置之后。
-
在执行测试方法后立即,但在测试拆除之前。
-
在特定测试框架的任何“之后”或“之后”方法之后。
-
在特定测试框架的任何“课后”或“毕竟”方法之后。
TestExecutionListener
TestExecutionListener
定义用于对
这TestContextManager
侦听器注册到的。看TestExecutionListener
配置.
上下文加载器
ContextLoader
是一个策略接口,用于加载ApplicationContext
对于
由 Spring TestContext 框架管理的集成测试。您应该实现SmartContextLoader
而不是这个接口来提供对组件类的支持,
活动 Bean 定义概要文件、测试属性源、上下文层次结构和WebApplicationContext
支持。
SmartContextLoader
是ContextLoader
取代
原始最小ContextLoader
SPI.具体来说,一个SmartContextLoader
可以选择
进程资源位置、组件类或上下文初始值设定项。此外,一个SmartContextLoader
可以在它加载的上下文中设置活动 Bean 定义配置文件和测试属性源。
Spring 提供了以下实现:
-
DelegatingSmartContextLoader
:两个默认加载器之一,它在内部委托给 一AnnotationConfigContextLoader
一个GenericXmlContextLoader
或GenericGroovyXmlContextLoader
,取决于为test 类声明的配置,或者是否存在默认位置或默认配置类。仅当 Groovy 位于类路径上时,才会启用 Groovy 支持。 -
WebDelegatingSmartContextLoader
:两个默认加载器之一,它在内部委托到AnnotationConfigWebContextLoader
一个GenericXmlWebContextLoader
或GenericGroovyXmlWebContextLoader
,取决于声明的配置测试类或是否存在默认位置或默认配置 类。 一张网ContextLoader
仅在以下情况下使用@WebAppConfiguration
存在于test 类上。仅当 Groovy 位于类路径上时,才会启用 Groovy 支持。 -
AnnotationConfigContextLoader
:加载标准ApplicationContext
从组件 类。 -
AnnotationConfigWebContextLoader
:加载一个WebApplicationContext
从组件 类。 -
GenericGroovyXmlContextLoader
:加载标准ApplicationContext
从资源位置是 Groovy 脚本或 XML 配置文件。 -
GenericGroovyXmlWebContextLoader
:加载一个WebApplicationContext
从资源位置是 Groovy 脚本或 XML 配置文件。 -
GenericXmlContextLoader
:加载标准ApplicationContext
从 XML 资源 地点。 -
GenericXmlWebContextLoader
:加载一个WebApplicationContext
从 XML 资源 地点。
3.5.2. 引导 TestContext 框架
Spring TestContext 框架内部的默认配置是
足以满足所有常见用例。但是,有时开发团队或
第三方框架想要更改默认值ContextLoader
,实现一个
习惯TestContext
或ContextCache
,扩充默认的ContextCustomizerFactory
和TestExecutionListener
实现,依此类推。为
对 TestContext 框架如何运行的这种低级控制,Spring 提供了一个
引导策略。
TestContextBootstrapper
定义用于引导 TestContext 框架的 SPI。一个TestContextBootstrapper
由TestContextManager
加载TestExecutionListener
实现,并构建TestContext
它管理。您可以为
测试类(或测试类层次结构),使用@BootstrapWith
,直接或作为
元注释。如果未使用@BootstrapWith
,要么DefaultTestContextBootstrapper
或WebTestContextBootstrapper
使用,具体取决于@WebAppConfiguration
.
由于TestContextBootstrapper
SPI 将来可能会发生变化(以适应
new 要求),我们强烈建议实现者不要实现此接口
直接,而是扩展AbstractTestContextBootstrapper
或其混凝土之一
子类。
3.5.3.TestExecutionListener
配置
Spring 提供了以下内容TestExecutionListener
已注册的实现
默认情况下,完全按以下顺序:
-
ServletTestExecutionListener
:配置 Servlet API 模拟WebApplicationContext
. -
DirtiesContextBeforeModesTestExecutionListener
:处理@DirtiesContext
“之前”模式的注释。 -
ApplicationEventsTestExecutionListener
:提供支持ApplicationEvents
. -
DependencyInjectionTestExecutionListener
:为测试提供依赖注入 实例。 -
DirtiesContextTestExecutionListener
:处理@DirtiesContext
注释 “之后”模式。 -
TransactionalTestExecutionListener
:提供事务性测试执行 默认回滚语义。 -
SqlScriptsTestExecutionListener
:运行使用@Sql
注解。 -
EventPublishingTestExecutionListener
:将测试执行事件发布到测试的ApplicationContext
(请参阅测试执行事件)。
注册TestExecutionListener
实现
您可以注册TestExecutionListener
测试类的显式实现,其子类及其嵌套类,使用@TestExecutionListeners
注解。 请参阅注释支持和 javadoc@TestExecutionListeners
了解详细信息和示例。
切换到默认值
TestExecutionListener 实现如果扩展一个用 Java
Kotlin
|
自动发现违约值TestExecutionListener
实现
注册TestExecutionListener
使用@TestExecutionListeners
是
适用于在有限测试场景中使用的自定义监听器。但是,它可以
如果需要在整个测试套件中使用自定义侦听器,则变得很麻烦。这
通过支持自动发现默认值来解决问题TestExecutionListener
通过SpringFactoriesLoader
机制。
具体来说,spring-test
模块声明所有核心默认值TestExecutionListener
在org.springframework.test.context.TestExecutionListener
键入
其META-INF/spring.factories
properties 文件。第三方框架和开发人员
可以贡献自己的TestExecutionListener
实现到默认列表
听众通过自己的方式META-INF/spring.factories
性能
文件。
订购TestExecutionListener
实现
当 TestContext 框架发现默认TestExecutionListener
实现
通过上述
SpringFactoriesLoader
机制,实例化的侦听器通过使用
Spring的AnnotationAwareOrderComparator
,这尊重了 Spring 的Ordered
interface 和@Order
排序的注释。AbstractTestExecutionListener
和所有默认值TestExecutionListener
Spring implementes提供的实现Ordered
跟
适当的值。因此,第三方框架和开发人员应确保
他们的默认值TestExecutionListener
实现按正确的顺序注册
通过实现Ordered
或声明@Order
.请参阅 javadoc 中的getOrder()
核心默认值的方法TestExecutionListener
实现,了解
值分配给每个核心侦听器。
合并TestExecutionListener
实现
如果自定义TestExecutionListener
通过@TestExecutionListeners
这
默认侦听器未注册。在大多数常见的测试场景中,这有效地
强制开发人员手动声明所有默认监听器以及任何自定义
听众。以下列表演示了这种配置样式:
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
MyCustomTestExecutionListener::class,
ServletTestExecutionListener::class,
DirtiesContextBeforeModesTestExecutionListener::class,
DependencyInjectionTestExecutionListener::class,
DirtiesContextTestExecutionListener::class,
TransactionalTestExecutionListener::class,
SqlScriptsTestExecutionListener::class
)
class MyTest {
// class body...
}
这种方法的挑战在于它要求开发人员确切地知道
默认注册哪些监听器。此外,默认监听器集可以
从一个版本到另一个版本的更改 — 例如,SqlScriptsTestExecutionListener
是 在 Spring Framework 4.1 中引入,以及DirtiesContextBeforeModesTestExecutionListener
是在 Spring Framework 4.2 中引入的。此外,像 SpringBoot 和 Spring Security 这样的第三方框架注册了自己的默认值TestExecutionListener
使用上述自动发现机制实现。
为了避免必须了解并重新声明所有默认监听器,您可以将mergeMode
属性@TestExecutionListeners
自MergeMode.MERGE_WITH_DEFAULTS
.MERGE_WITH_DEFAULTS
表示本地声明的侦听器应与default listeners 合并。合并算法确保从list 中删除重复项,并且生成的合并侦听器集根据语义进行排序 之AnnotationAwareOrderComparator
,如订购TestExecutionListener
实现. 如果侦听器实现Ordered
或用@Order
,它会影响它与默认值合并的位置。否则,本地声明的侦听器在合并时将附加到默认侦听器列表中。
例如,如果MyCustomTestExecutionListener
class 在前面的示例中配置其order
值(例如,500
) 小于ServletTestExecutionListener
(恰好是1000
)、MyCustomTestExecutionListener
然后可以自动与列表合并defaults 在ServletTestExecutionListener
,并且前面的示例可以替换为以下内容:
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
listeners = [MyCustomTestExecutionListener::class],
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
3.5.4. 应用程序事件
从 Spring Framework 5.3.3 开始,TestContext 框架支持记录在ApplicationContext
以便可以针对
测试。在执行单个测试期间发布的所有事件都可以通过以下方式提供
这ApplicationEvents
API,允许您将事件处理为java.util.Stream
.
使用ApplicationEvents
在测试中,执行以下作。
-
确保测试类已使用
@RecordApplicationEvents
. -
确保
ApplicationEventsTestExecutionListener
已注册。但是请注意, 那ApplicationEventsTestExecutionListener
默认注册,只需要 如果您通过@TestExecutionListeners
不包括默认侦听器。 -
批注类型
ApplicationEvents
跟@Autowired
并使用ApplicationEvents
在测试和生命周期方法中(例如@BeforeEach
和@AfterEach
方法)。-
当使用 SpringExtension for JUnit Jupiter 时,您可以声明一个方法 type 的参数
ApplicationEvents
在测试或生命周期方法中作为替代方法 设置为@Autowired
字段。
-
以下测试类使用SpringExtension
用于 JUnit Jupiter 和 AssertJ 来断言应用程序事件的类型
在 Spring 托管组件中调用方法时发布:
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {
@Autowired
OrderService orderService;
@Autowired
ApplicationEvents events; (2)
@Test
void submitOrder() {
// Invoke method in OrderService that publishes an event
orderService.submitOrder(new Order(/* ... */));
// Verify that an OrderSubmitted event was published
long numEvents = events.stream(OrderSubmitted.class).count(); (3)
assertThat(numEvents).isEqualTo(1);
}
}
1 | 用@RecordApplicationEvents . |
2 | 注入ApplicationEvents 实例。 |
3 | 使用ApplicationEvents 用于计算数量的 APIOrderSubmitted 事件已发布。 |
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {
@Autowired
lateinit var orderService: OrderService
@Autowired
lateinit var events: ApplicationEvents (2)
@Test
fun submitOrder() {
// Invoke method in OrderService that publishes an event
orderService.submitOrder(Order(/* ... */))
// Verify that an OrderSubmitted event was published
val numEvents = events.stream(OrderSubmitted::class).count() (3)
assertThat(numEvents).isEqualTo(1)
}
}
1 | 用@RecordApplicationEvents . |
2 | 注入ApplicationEvents 实例。 |
3 | 使用ApplicationEvents 用于计算数量的 APIOrderSubmitted 事件已发布。 |
请参阅ApplicationEvents
Java文档有关ApplicationEvents
应用程序接口。
3.5.5. 测试执行事件
这EventPublishingTestExecutionListener
Spring Framework 5.2 中引入的
实现自定义TestExecutionListener
.组件
测试的ApplicationContext
可以监听EventPublishingTestExecutionListener
,每个方法对应于TestExecutionListener
应用程序接口。
-
BeforeTestClassEvent
-
PrepareTestInstanceEvent
-
BeforeTestMethodEvent
-
BeforeTestExecutionEvent
-
AfterTestExecutionEvent
-
AfterTestMethodEvent
-
AfterTestClassEvent
这些事件可能出于各种原因被使用,例如重置模拟 Bean 或跟踪
测试执行。使用测试执行事件而不是实现测试执行事件的一个优点
一个习惯TestExecutionListener
测试执行事件可以被任何
在测试中注册的春豆ApplicationContext
,这些Beans可能会受益
直接来自依赖注入和ApplicationContext
.在
对比,一个TestExecutionListener
不是ApplicationContext
.
这 因此,一个 如果您希望确保 同样,如果 |
为了监听测试执行事件,Spring bean 可以选择实现org.springframework.context.ApplicationListener
接口。或者,听众
方法可以用@EventListener
并配置为监听其中一个
上面列出的特定事件类型(请参阅基于注释的事件侦听器)。
由于这种方法的流行,Spring 提供了以下专用@EventListener
注释,以简化测试执行事件侦听器的注册。
这些注释驻留在org.springframework.test.context.event.annotation
包。
-
@BeforeTestClass
-
@PrepareTestInstance
-
@BeforeTestMethod
-
@BeforeTestExecution
-
@AfterTestExecution
-
@AfterTestMethod
-
@AfterTestClass
异常处理
默认情况下,如果测试执行事件监听器在使用
事件,则该异常将传播到正在使用的底层测试框架(例如
JUnit 或 TestNG)。例如,如果消耗BeforeTestMethodEvent
结果
异常,相应的测试方法将因异常而失败。在
相反,如果异步测试执行事件监听器抛出异常,则
异常不会传播到底层测试框架。有关
异步异常处理,请参阅类级 Javadoc@EventListener
.
异步侦听器
如果您希望特定的测试执行事件监听器异步处理事件,您可以使用 Spring 的定期@Async
支持.有关更多详细信息,请参阅类级 javadoc@EventListener
.
3.5.6. 上下文管理
每TestContext
为测试实例提供上下文管理和缓存支持
它对此负责。测试实例不会自动接收对
配置ApplicationContext
.但是,如果测试类实现了ApplicationContextAware
接口,对ApplicationContext
提供
到测试实例。请注意AbstractJUnit4SpringContextTests
和AbstractTestNGSpringContextTests
实现ApplicationContextAware
因此,
提供对ApplicationContext
自然而然。
@Autowired ApplicationContext
作为实现 Java
Kotlin
同样,如果您的测试配置为加载 Java
Kotlin
使用 |
使用 TestContext 框架的测试类不需要扩展任何特定的类或实现特定接口来配置其应用程序上下文。 相反 配置是通过声明@ContextConfiguration
注释class 级别。如果您的测试类没有显式声明应用程序上下文资源locations 或组件类,则配置的ContextLoader
确定如何加载上下文,从默认位置或默认配置类。除了上下文资源位置和组件类,还可以配置应用程序上下文通过应用程序上下文初始值设定项。
以下部分解释如何使用 Spring 的@ContextConfiguration
注释到
配置测试ApplicationContext
通过使用 XML 配置文件、Groovy 脚本、
组件类(通常@Configuration
类)或上下文初始值设定项。
或者,您可以实现和配置自己的自定义SmartContextLoader
为
高级用例。
使用 XML 资源进行上下文配置
要加载ApplicationContext
对于使用 XML 配置文件进行的测试,请注释
您的测试类使用@ContextConfiguration
并配置locations
属性替换为
包含 XML 配置元数据的资源位置的数组。普通或
相对路径(例如context.xml
) 被视为类路径资源,即
相对于定义测试类的包。以斜杠开头的路径
被视为绝对类路径位置(例如,/org/example/config.xml
).一个
表示资源 URL 的路径(即,以classpath:
,file:
,http:
等)按原样使用。
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | 将 locations 属性设置为 XML 文件列表。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
// class body...
}
1 | 将 locations 属性设置为 XML 文件列表。 |
@ContextConfiguration
支持locations
属性通过
标准 Javavalue
属性。因此,如果您不需要声明额外的
属性中的@ContextConfiguration
,您可以省略locations
属性名称,并使用简写格式声明资源位置
如以下示例所示:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | 指定 XML 文件而不使用location 属性。 |
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
// class body...
}
1 | 指定 XML 文件而不使用location 属性。 |
如果同时省略locations
和value
属性@ContextConfiguration
注释,TestContext 框架会尝试检测默认的
XML 资源位置。具体说来GenericXmlContextLoader
和GenericXmlWebContextLoader
根据测试名称检测默认位置
类。如果您的类名为com.example.MyTest
,GenericXmlContextLoader
加载您的
应用程序上下文"classpath:com/example/MyTest-context.xml"
.以下内容
示例显示了如何执行此作:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
使用 Groovy 脚本进行上下文配置
要加载ApplicationContext
对于使用使用 Groovy Bean 定义 DSL 的 Groovy 脚本进行测试,您可以对
您的测试类使用@ContextConfiguration
并配置locations
或value
属性,其中包含 Groovy 脚本的资源位置的数组。资源
Groovy 脚本的查找语义与 XML 配置文件描述的语义相同。
启用 Groovy 脚本支持 支持使用 Groovy 脚本加载ApplicationContext 在Spring
如果 Groovy 在类路径上,则会自动启用 TestContext 框架。 |
以下示例显示了如何指定 Groovy 配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
// class body...
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") (1)
class MyTest {
// class body...
}
1 | 指定 Groovy 配置文件的位置。 |
如果同时省略locations
和value
属性@ContextConfiguration
注释时,TestContext 框架会尝试检测默认的 Groovy 脚本。
具体说来GenericGroovyXmlContextLoader
和GenericGroovyXmlWebContextLoader
根据测试类的名称检测默认位置。如果您的类名为com.example.MyTest
,Groovy 上下文加载器从"classpath:com/example/MyTestContext.groovy"
.以下示例演示如何使用
默认值:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
同时声明 XML 配置和 Groovy 脚本
您可以使用以下命令同时声明 XML 配置文件和 Groovy 脚本
这 以下列表显示了如何在集成测试中组合两者: Java
Kotlin
|
使用组件类进行上下文配置
要加载ApplicationContext
对于使用组件类的测试(请参阅基于 Java 的容器配置),您可以注释测试
类与@ContextConfiguration
并配置classes
属性与数组
包含对组件类的引用。以下示例显示了如何执行此作:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
// class body...
}
1 | 指定组件类。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) (1)
class MyTest {
// class body...
}
1 | 指定组件类。 |
组件类
术语“组件类”可以指以下任何一项:
请参阅 |
如果您省略classes
属性@ContextConfiguration
注释,则
TestContext 框架尝试检测默认配置类的存在。
具体说来AnnotationConfigContextLoader
和AnnotationConfigWebContextLoader
全部检测static
测试类的嵌套类,满足
配置类实现,如@Configuration
javadoc 的文档。
请注意,配置类的名称是任意的。此外,测试类可以
包含多个static
嵌套配置类(如果需要)。在以下内容中
示例,OrderServiceTest
类声明一个static
嵌套配置类
叫Config
自动用于加载ApplicationContext
用于测试
类:
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {
@Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
OrderService orderService;
@Test
void testOrderService() {
// test the orderService
}
}
1 | 从嵌套的Config 类。 |
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the nested Config class
class OrderServiceTest {
@Autowired
lateinit var orderService: OrderService
@Configuration
class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
fun orderService(): OrderService {
// set properties, etc.
return OrderServiceImpl()
}
}
@Test
fun testOrderService() {
// test the orderService
}
}
1 | 从嵌套的Config 类。 |
混合 XML、Groovy 脚本和组件类
有时可能需要混合使用 XML 配置文件、Groovy 脚本和
组件类(通常@Configuration
classes)来配置ApplicationContext
用于您的测试。例如,如果您在
生产,您可以决定要使用@Configuration
要配置的类
特定的 Spring 管理组件,反之亦然。
此外,一些第三方框架(如 Spring Boot)提供了一流的
支持加载ApplicationContext
来自不同类型的资源
同时(例如,XML 配置文件、Groovy 脚本和@Configuration
类)。从历史上看,Spring Framework 不支持这一点
标准部署。因此,大多数SmartContextLoader
实现
Spring Framework 在spring-test
模块仅支持一种资源类型
对于每个测试上下文。但是,这并不意味着您不能同时使用两者。一
一般规则的例外是GenericGroovyXmlContextLoader
和GenericGroovyXmlWebContextLoader
同时支持 XML 配置文件和 Groovy
同时编写脚本。此外,第三方框架可以选择支持
两者的声明locations
和classes
通过@ContextConfiguration
,并且,使用
TestContext 框架中的标准测试支持,您有以下选项。
如果要使用资源位置(例如,XML 或 Groovy)和@Configuration
类配置测试时,您必须选择一个作为入口点,并且必须包含或导入另一个。例如,在 XML 或 Groovy 脚本中,您可以包含@Configuration
类,通过使用组件扫描或将它们定义为普通 Springbean,而在@Configuration
类,您可以使用@ImportResource
导入 XML配置文件或 Groovy 脚本。请注意,此行为在语义上是等效的到如何在生产环境中配置应用程序:在生产配置中,您定义一组 XML 或 Groovy 资源位置或一组@Configuration
您的生产所来自的类ApplicationContext
,但您仍然可以自由地包含或导入其他类型的配置。
使用上下文初始值设定项进行上下文配置
要配置ApplicationContext
使用上下文初始值设定项对测试进行测试,使用@ContextConfiguration
并配置initializers
属性,其中包含对实现的类的引用ApplicationContextInitializer
.然后,声明的上下文初始值设定项用于
初始化ConfigurableApplicationContext
为测试加载的。请注意
具体ConfigurableApplicationContext
每个声明的初始值设定项支持的类型
必须与ApplicationContext
由SmartContextLoader
使用中(通常GenericApplicationContext
).此外,
调用初始值设定项的顺序取决于它们是否实现了 Spring 的Ordered
接口或使用 Spring 的@Order
注释或标准@Priority
注解。以下示例演示如何使用初始值设定项:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class) (1)
class MyTest {
// class body...
}
1 | 使用配置类和初始值设定项指定配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = [TestConfig::class],
initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
// class body...
}
1 | 使用配置类和初始值设定项指定配置。 |
您还可以省略 XML 配置文件、Groovy 脚本或组件类的声明@ContextConfiguration
完全声明,而仅声明ApplicationContextInitializer
类,然后负责注册 bean在上下文中——例如,通过以编程方式从 XML 加载 bean 定义文件或配置类。以下示例显示了如何执行此作:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
// class body...
}
1 | 仅使用初始值设定项指定配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
// class body...
}
1 | 仅使用初始值设定项指定配置。 |
上下文配置继承
@ContextConfiguration
支持布尔值inheritLocations
和inheritInitializers
属性,表示是资源位置还是组件类和上下文应继承超类声明的初始值设定项。两个flags 的默认值为true
. 这意味着测试类继承资源位置或组件类以及任何超类声明的上下文初始值设定项。具体来说,测试类的资源位置或组件类被附加到超类声明的资源位置或带注释的类的列表中。同样,给定测试类的初始值设定项被添加到初始值设定项集由测试超类定义。因此,子类可以选择扩展资源位置、组件类或上下文初始值设定项。
如果inheritLocations
或inheritInitializers
属性@ContextConfiguration
设置为false
、资源位置或组件类和上下文初始值设定项,分别用于测试类 shadow,并有效地替换超类定义的配置。
从 Spring Framework 5.3 开始,测试配置也可以从封闭 类。 看@Nested 测试类配置了解详情。 |
在下一个使用 XML 资源位置的示例中,ApplicationContext
为ExtendedTest
加载自base-config.xml
和extended-config.xml
,按此顺序。在extended-config.xml
因此,可以覆盖(即替换)那些定义在base-config.xml
. 以下示例显示了一个类如何扩展另一个类,并同时使用自己的配置文件和超类的配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 超类中定义的配置文件。 |
2 | 子类中定义的配置文件。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | 超类中定义的配置文件。 |
2 | 子类中定义的配置文件。 |
同样,在下一个使用组件类的示例中,ApplicationContext
为ExtendedTest
从BaseConfig
和ExtendedConfig
类,在
次序。在ExtendedConfig
因此,可以覆盖(即替换)
中定义的BaseConfig
. 以下示例显示了一个类如何扩展另一个类,并同时使用自己的配置类和超类的配置类:
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 超类中定义的配置类。 |
2 | 子类中定义的配置类。 |
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig::class) (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | 超类中定义的配置类。 |
2 | 子类中定义的配置类。 |
在下一个使用上下文初始值设定项的示例中,ApplicationContext
为ExtendedTest
通过使用BaseInitializer
和ExtendedInitializer
.注意
但是,调用初始值设定项的顺序取决于它们是否
实现 Spring 的Ordered
接口或使用 Spring 的@Order
注解
或标准@Priority
注解。以下示例显示了一个类如何
扩展另一个并使用它自己的初始值设定项和超类的初始值设定项:
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 超类中定义的初始值设定项。 |
2 | 子类中定义的初始值设定项。 |
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | 超类中定义的初始值设定项。 |
2 | 子类中定义的初始值设定项。 |
使用环境配置文件进行上下文配置
Spring Framework 对环境和配置文件的概念具有一流的支持
(又名“bean 定义配置文件”),并且可以配置集成测试以激活
适用于各种测试场景的特定 Bean 定义配置文件。这是通过以下方式实现的
使用@ActiveProfiles
注释并提供列表
加载时应激活的配置文件ApplicationContext
用于测试。
您可以使用@ActiveProfiles 与SmartContextLoader SPI 的@ActiveProfiles 不支持旧版的实现ContextLoader SPI. |
考虑两个 XML 配置和@Configuration
类:
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
什么时候TransferServiceTest
运行,则其ApplicationContext
从app-config.xml
类路径根目录中的配置文件。如果您检查app-config.xml
,您可以看到accountRepository
bean 依赖于dataSource
豆。然而dataSource
未定义为顶级 Bean。相反dataSource
定义了三次:在production
profile,在dev
轮廓
并在default
轮廓。
通过注释TransferServiceTest
跟@ActiveProfiles("dev")
,我们指示Spring
TestContext 框架来加载ApplicationContext
将活动配置文件设置为{"dev"}
.因此,将创建一个嵌入式数据库并填充测试数据,并且
这accountRepository
bean 与开发的参考DataSource
.
这很可能就是我们在集成测试中想要的。
有时将 bean 分配给default
轮廓。 默认profile 中的 bean 仅当没有专门激活其他配置文件时才会被包括在内。您可以使用this 来定义要在应用程序默认状态中使用的“回退”bean。 为 例如,您可以显式提供dev
和production
配置 文件 但是,当内存中数据源都不处于活动状态时,将内存中数据源定义为默认值。
以下代码列表演示了如何实现相同的配置和集成测试@Configuration
类而不是 XML:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("dev")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
@Configuration
class TransferServiceConfig {
@Autowired
lateinit var dataSource: DataSource
@Bean
fun transferService(): TransferService {
return DefaultTransferService(accountRepository(), feePolicy())
}
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun feePolicy(): FeePolicy {
return ZeroFeePolicy()
}
}
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
在此变体中,我们将 XML 配置拆分为四个独立的@Configuration
类:
-
TransferServiceConfig
:获得一个dataSource
通过使用@Autowired
. -
StandaloneDataConfig
:定义dataSource
适用于适用于 开发人员测试。 -
JndiDataConfig
:定义dataSource
在生产中从 JNDI 检索的 环境。 -
DefaultDataConfig
:定义dataSource
对于默认的嵌入式数据库,如果没有 配置文件处于活动状态。
与基于 XML 的配置示例一样,我们仍然对TransferServiceTest
跟@ActiveProfiles("dev")
,但这次我们通过
使用@ContextConfiguration
注解。测试类本身的正文保留
完全没有变化。
通常情况下,一组配置文件在多个测试类中使用
在给定项目中。因此,为了避免重复声明@ActiveProfiles
注释,您可以声明@ActiveProfiles
一次在基类上,以及子类
自动继承@ActiveProfiles
基类中的配置。在
以下示例,声明@ActiveProfiles
(以及其他注释)
已移动到抽象超类,AbstractIntegrationTest
:
从 Spring Framework 5.3 开始,测试配置也可以从封闭 类。 看@Nested 测试类配置了解详情。 |
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
@ActiveProfiles
还支持inheritProfiles
属性,可用于
禁用活动配置文件的继承,如以下示例所示:
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
// test body
}
此外,有时需要解析活动配置文件以进行测试 以编程方式而不是声明方式——例如,基于:
-
当前作系统。
-
测试是否在持续集成构建服务器上运行。
-
某些环境变量的存在。
-
存在自定义类级注释。
-
其他问题。
要以编程方式解析活动 Bean 定义配置文件,您可以实现
一个习惯ActiveProfilesResolver
并使用resolver
属性@ActiveProfiles
.有关更多信息,请参阅相应的 javadoc。
以下示例演示如何实现和注册自定义OperatingSystemActiveProfilesResolver
:
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver::class,
inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
// test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profile = ...;
// determine the value of profile based on the operating system
return new String[] {profile};
}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {
override fun resolve(testClass: Class<*>): Array<String> {
val profile: String = ...
// determine the value of profile based on the operating system
return arrayOf(profile)
}
}
使用测试属性源的上下文配置
Spring Framework 对具有
属性源的层次结构,并且您可以使用特定于测试的集成测试来配置集成测试
属性来源。与@PropertySource
用于@Configuration
类,您可以声明@TestPropertySource
测试上的注释
class 来声明测试属性文件或内联属性的资源位置。
这些测试属性源将添加到PropertySources
在Environment
对于ApplicationContext
加载以进行带注释的集成测试。
您可以使用 实现 |
声明测试属性源
您可以使用locations
或value
属性@TestPropertySource
.
支持传统和基于 XML 的属性文件格式,例如"classpath:/com/example/test.properties"
或"file:///path/to/file.xml"
.
每条路径都被解释为一个弹簧Resource
. 普通路径(例如"test.properties"
)被视为相对于包定义测试类的类路径资源。以斜杠开头的路径被视为absolute 类路径资源(例如:"/org/example/test.xml"
). 路径引用 URL(例如,以classpath:
,file:
或http:
) 是使用指定的资源协议加载。资源位置通配符(例如*/.properties
) 不允许:每个位置的计算结果必须恰好为 1.properties
或.xml
资源。
以下示例使用测试属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 指定具有绝对路径的属性文件。 |
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 指定具有绝对路径的属性文件。 |
您可以使用properties
属性@TestPropertySource
,如下一个示例所示。都
键值对被添加到封闭的Environment
作为单次测试PropertySource
具有最高优先级。
键值对支持的语法与为 Java 属性文件:
-
key=value
-
key:value
-
key value
以下示例设置两个内联属性:
@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 开始, 此外,您可以在测试类上声明多个组合注释,每个注释都是
meta-annotated 直接存在 |
默认属性文件检测
如果@TestPropertySource
被声明为空注解(即,没有显式
值locations
或properties
属性),则尝试检测
默认属性文件。例如
如果带注释的测试类是com.example.MyTest
,相应的默认属性
file 是classpath:com/example/MyTest.properties
.如果无法检测到默认值,则IllegalStateException
被抛出。
优先
测试属性的优先级高于作系统的
环境、Java 系统属性或应用程序添加的属性源
声明性地使用@PropertySource
或以编程方式。因此,测试属性可以
用于有选择地覆盖从系统和应用程序属性加载的属性
来源。此外,内联属性的优先级高于加载的属性
从资源位置。但请注意,通过@DynamicPropertySource
有
比通过@TestPropertySource
.
在下一个示例中,timezone
和port
属性和"/test.properties"
覆盖在系统中定义的任何同名属性
和应用程序属性源。此外,如果"/test.properties"
文件定义
条目timezone
和port
被内联
使用properties
属性。以下示例演示了如何
要在文件和内联中指定属性:
@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
.这意味着测试类继承位置和内联属性
由任何超类声明。具体来说,位置和内联属性
test 类附加到超类声明的位置和内联属性。
因此,子类可以选择扩展位置和内联属性。注意
稍后出现的属性 shadow (即覆盖) 同名属性
出现得更早。此外,上述优先规则适用于继承的
测试属性源。
如果inheritLocations
或inheritProperties
属性@TestPropertySource
是
设置为false
,分别是测试类的位置或内联属性
shadow 并有效地替换超类定义的配置。
从 Spring Framework 5.3 开始,测试配置也可以从封闭 类。 看@Nested 测试类配置了解详情。 |
在下一个示例中,ApplicationContext
为BaseTest
仅使用base.properties
文件作为测试属性源。相比之下,ApplicationContext
为ExtendedTest
通过使用base.properties
和extended.properties
文件作为测试属性源位置。以下示例演示如何定义
使用properties
文件:
@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() {
// ...
}
在下一个示例中,ApplicationContext
为BaseTest
仅使用
内联key1
财产。相比之下,ApplicationContext
为ExtendedTest
是
使用内联key1
和key2
性能。以下示例演示了如何
要使用内联属性定义子类及其超类中的属性:
@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() {
// ...
}
使用动态属性源进行上下文配置
从 Spring Framework 5.2.5 开始,TestContext 框架通过@DynamicPropertySource
注解。此注释可用于
需要将具有动态值的属性添加到PropertySources
在Environment
对于ApplicationContext
loaded 为
集成测试。
这 |
与@TestPropertySource
在类级别应用的注释,@DynamicPropertySource
必须申请
设置为static
接受单个DynamicPropertyRegistry
参数,即
用于将名称值对添加到Environment
.值是动态的,通过
一个Supplier
仅在解析属性时调用。通常,方法
引用用于提供值,如以下示例所示,该示例使用
Testcontainers 项目来管理 Spring 之外的 Redis 容器ApplicationContext
.托管 Redis 容器的 IP 地址和端口
可用于测试的ApplicationContext
通过redis.host
和redis.port
性能。这些属性可以通过 Spring 的Environment
抽象或直接注入到 Spring 管理的组件中——例如,通过@Value("${redis.host}")
和@Value("${redis.port}")
分别。
如果您使用 |
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
@Container
static GenericContainer redis =
new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("redis.host", redis::getHost);
registry.add("redis.port", redis::getFirstMappedPort);
}
// tests ...
}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
companion object {
@Container
@JvmStatic
val redis: GenericContainer =
GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379)
@DynamicPropertySource
@JvmStatic
fun redisProperties(registry: DynamicPropertyRegistry) {
registry.add("redis.host", redis::getHost)
registry.add("redis.port", redis::getFirstMappedPort)
}
}
// tests ...
}
加载一个WebApplicationContext
要指示 TestContext 框架加载WebApplicationContext
而不是
标准ApplicationContext
,您可以使用@WebAppConfiguration
.
的存在@WebAppConfiguration
在测试类上指示 TestContext
框架 (TCF) 将WebApplicationContext
(WAC) 应该为您的
集成测试。在后台,TCF 确保MockServletContext
是
创建并提供给测试的 WAC。默认情况下,您的基本资源路径MockServletContext
设置为src/main/webapp
.这被解释为路径相对
到 JVM 的根目录(通常是项目的路径)。如果您熟悉
Maven 项目中 Web 应用程序的目录结构,您知道src/main/webapp
是 WAR 根的默认位置。如果您需要覆盖此默认值,则可以提供@WebAppConfiguration
注释(例如,@WebAppConfiguration("src/test/webapp")
).如果您愿意
从类路径而不是文件系统引用基本资源路径,您可以使用
Spring的classpath:
前缀。
请注意,Spring 对WebApplicationContext
实现不相上下
支持标准ApplicationContext
实现。使用WebApplicationContext
,您可以自由声明 XML 配置文件、Groovy 脚本、
或@Configuration
类,使用@ContextConfiguration
.您也可以免费使用
任何其他测试注释,例如@ActiveProfiles
,@TestExecutionListeners
,@Sql
,@Rollback
,等。
本节中的其余示例显示了一些
加载一个WebApplicationContext
.以下示例显示了 TestContext
框架对约定胜过配置的支持:
@ExtendWith(SpringExtension.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
//...
}
如果使用@WebAppConfiguration
不指定资源
base 路径,资源路径实际上默认为file:src/main/webapp
.同样地
如果您声明@ContextConfiguration
不指定资源locations
元件classes
或上下文initializers
,Spring 会尝试检测
使用约定进行配置(即WacTests-context.xml
在同一个包中
作为WacTests
类或静态嵌套@Configuration
类)。
以下示例演示如何使用@WebAppConfiguration
以及 XML 资源位置@ContextConfiguration
:
@ExtendWith(SpringExtension.class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
这里要注意的重要一点是,这两个路径的不同语义
附注。默认情况下,@WebAppConfiguration
资源路径是基于文件系统的,
而@ContextConfiguration
资源位置基于类路径。
以下示例显示,我们可以重写两者的默认资源语义 通过指定 Spring 资源前缀来注释:
@ExtendWith(SpringExtension.class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//...
}
将此示例中的注释与前面的示例进行对比。
为了提供全面的 Web 测试支持,TestContext 框架有一个ServletTestExecutionListener
默认启用。当针对WebApplicationContext
这TestExecutionListener
使用 Spring Web 的RequestContextHolder
以前
每个测试方法并创建一个MockHttpServletRequest
一个MockHttpServletResponse
和
一个ServletWebRequest
基于配置的 Base 资源路径@WebAppConfiguration
.ServletTestExecutionListener
还确保MockHttpServletResponse
和ServletWebRequest
可以注入到测试实例中,
并且,一旦测试完成,它就会清理线程本地状态。
一旦你有一个WebApplicationContext
加载测试,您可能会发现
需要与 Web 模拟进行交互——例如,设置测试夹具或
调用 Web 组件后执行断言。以下示例显示了哪些
模拟可以自动连接到您的测试实例中。请注意,WebApplicationContext
和MockServletContext
都缓存在整个测试套件中,而其他模拟是
通过ServletTestExecutionListener
.
@SpringJUnitWebConfig
class WacTests {
@Autowired
WebApplicationContext wac; // cached
@Autowired
MockServletContext servletContext; // cached
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
@SpringJUnitWebConfig
class WacTests {
@Autowired
lateinit var wac: WebApplicationContext // cached
@Autowired
lateinit var servletContext: MockServletContext // cached
@Autowired
lateinit var session: MockHttpSession
@Autowired
lateinit var request: MockHttpServletRequest
@Autowired
lateinit var response: MockHttpServletResponse
@Autowired
lateinit var webRequest: ServletWebRequest
//...
}
上下文缓存
一旦 TestContext 框架加载了ApplicationContext
(或WebApplicationContext
)
对于测试,该上下文将被缓存并重用于声明
同一测试套件中的相同唯一上下文配置。要了解缓存如何
有效,了解“唯一”和“测试套件”的含义很重要。
一ApplicationContext
可以通过配置组合进行唯一标识
参数。因此,独特的配置组合
parameters 用于生成缓存上下文的键。测试上下文
框架使用以下配置参数来构建上下文缓存键:
-
locations
(从@ContextConfiguration
) -
classes
(从@ContextConfiguration
) -
contextInitializerClasses
(从@ContextConfiguration
) -
contextCustomizers
(从ContextCustomizerFactory
) – 这包括@DynamicPropertySource
方法以及 Spring Boot 的 测试支持,例如@MockBean
和@SpyBean
. -
contextLoader
(从@ContextConfiguration
) -
parent
(从@ContextHierarchy
) -
activeProfiles
(从@ActiveProfiles
) -
propertySourceLocations
(从@TestPropertySource
) -
propertySourceProperties
(从@TestPropertySource
) -
resourceBasePath
(从@WebAppConfiguration
)
例如,如果TestClassA
指定{"app-config.xml", "test-config.xml"}
对于locations
(或value
) 属性的@ContextConfiguration
、TestContext 框架
加载相应的ApplicationContext
并将其存储在static
上下文缓存
在仅基于这些位置的密钥下。所以,如果TestClassB
还定义了{"app-config.xml", "test-config.xml"}
对于其位置(显式或
通过继承隐式地),但没有定义@WebAppConfiguration
,不同的ContextLoader
、不同的活动配置文件、不同的上下文初始值设定项、不同的
测试属性源,或不同的父上下文,则相同ApplicationContext
由两个测试类共享。这意味着加载应用程序的设置成本
上下文仅产生一次(每个测试套件),并且后续测试执行
更快。
测试套件和分叉进程
Spring TestContext 框架将应用程序上下文存储在静态缓存中。这
意味着上下文实际上存储在 若要从缓存机制中受益,所有测试都必须在同一进程或测试中运行
套房。这可以通过在 IDE 中作为一个组执行所有测试来实现。同样地
当使用 Ant、Maven 或 Gradle 等构建框架执行测试时,它是
确保生成框架不会在测试之间分叉非常重要。例如
如果 |
上下文缓存的大小以默认最大大小 32 为限。每当
达到最大大小时,使用最近最少使用的 (LRU) 逐出策略来逐出和
关闭过时的上下文。您可以从命令行或构建中配置最大大小
脚本,通过设置名为spring.test.context.cache.maxSize
.作为
或者,您可以通过SpringProperties
机制。
由于在给定的测试套件中加载了大量应用程序上下文可以
导致套件运行时间过长,这通常是有益的
确切地知道已经加载和缓存了多少上下文。要查看
底层上下文缓存,您可以为org.springframework.test.context.cache
日志记录类别设置为DEBUG
.
在极少数情况下,测试会损坏应用程序上下文并需要重新加载
(例如,通过修改 Bean 定义或应用程序对象的状态),您可以
可以使用@DirtiesContext
(参见@DirtiesContext
在春季测试中
注释)。这指示 Spring 从缓存中删除上下文并重新构建
运行需要相同应用程序的下一个测试之前的应用程序上下文
上下文。请注意,对@DirtiesContext
注释由DirtiesContextBeforeModesTestExecutionListener
和DirtiesContextTestExecutionListener
,默认启用。
ApplicationContext 生命周期和控制台日志记录
当您需要调试使用 Spring TestContext Framework 执行的测试时,它可以是
有助于分析控制台输出(即,输出到 关于由 Spring Framework 本身或组件触发的控制台日志记录
在 这 这
如果上下文根据 当弹簧 |
上下文层次结构
在编写依赖于加载的 Spring 的集成测试时ApplicationContext
是的 通常足以针对单个上下文进行测试。但是,有时它是有益甚至有必要针对ApplicationContext
实例。 例如,如果您正在开发一个 Spring MVC Web 应用程序,您通常有一个根WebApplicationContext
由 Spring 的ContextLoaderListener
和
孩子WebApplicationContext
由 Spring 的DispatcherServlet
.这导致
父子上下文层次结构,其中共享组件和基础结构配置
在根上下文中声明,并由特定于 Web 的子上下文中使用
组件。另一个用例可以在 Spring Batch 应用程序中找到,您经常在其中
具有为共享批处理基础结构提供配置的父上下文和
用于配置特定批处理作业的子上下文。
您可以通过声明上下文来编写使用上下文层次结构的集成测试
配置将@ContextHierarchy
注释,无论是在单个测试类上
或在测试类层次结构中。如果在多个类上声明了上下文层次结构
在测试类层次结构中,还可以合并或覆盖上下文配置
对于上下文层次结构中的特定命名级别。合并配置时
给定级别,配置资源类型(即 XML 配置
文件或组件类)必须一致。否则,完全可以接受
在使用不同资源类型配置的上下文层次结构中具有不同的级别。
本节中其余基于 JUnit Jupiter 的示例显示了常见配置 需要使用上下文层次结构的集成测试方案。
ControllerIntegrationTests
表示
Spring MVC Web 应用程序通过声明由两个级别组成的上下文层次结构,
一个用于根WebApplicationContext
(通过使用TestAppConfig
@Configuration
类),一个用于调度程序 servletWebApplicationContext
(通过使用WebConfig
@Configuration
类)。这WebApplicationContext
自动连接到测试实例的 is 是子上下文的 (即
层次结构中的最低上下文)。以下列表显示了此配置方案:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
@Autowired
WebApplicationContext wac;
// ...
}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [TestAppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {
@Autowired
lateinit var wac: WebApplicationContext
// ...
}
此示例中的测试类在测试类中定义上下文层次结构
等级制度。AbstractWebTests
声明根的配置WebApplicationContext
在 Spring 驱动的 Web 应用程序中。但请注意,AbstractWebTests
不声明@ContextHierarchy
.因此,子类AbstractWebTests
可以选择参与上下文层次结构或遵循
标准语义@ContextConfiguration
.SoapWebServiceTests
和RestWebServiceTests
两者都延伸AbstractWebTests
并通过以下方式定义上下文层次结构
用@ContextHierarchy
.结果是加载了三个应用程序上下文(一个
对于每个声明@ContextConfiguration
),以及加载的应用程序上下文
基于中的配置AbstractWebTests
被设置为每个
为具体子类加载的上下文。以下列表显示了这一点
配置场景:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests
@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()
@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
此示例中的类展示了使用命名层次结构级别来合并
上下文层次结构中特定级别的配置。BaseTests
定义两个级别
在层次结构中,parent
和child
.ExtendedTests
延伸BaseTests
并指示
Spring TestContext 框架来合并child
层次结构级别,通过确保在name
属性@ContextConfiguration
都是child
.结果是三个应用程序上下文
已加载:一个用于/app-config.xml
,一个用于/user-config.xml
,一个用于{"/user-config.xml", "/order-config.xml"}
.与前面的示例一样,该
应用程序上下文从/app-config.xml
设置为
从/user-config.xml
和{"/user-config.xml", "/order-config.xml"}
.
以下列表显示了此配置方案:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
与前面的示例相比,此示例演示了如何覆盖
通过设置inheritLocations
标记@ContextConfiguration
自false
.因此,
应用程序上下文ExtendedTests
仅从/test-user-config.xml
和
将其父级设置为从以下位置加载的上下文/app-config.xml
.以下列表
显示了以下配置方案:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(
name = "child",
locations = ["/test-user-config.xml"],
inheritLocations = false
))
class ExtendedTests : BaseTests() {}
在上下文层次结构中弄脏上下文 如果您使用@DirtiesContext 在上下文配置为context 层次结构的一部分的测试中,您可以使用hierarchyMode 标志来控制上下文缓存被清除的方式。有关更多详细信息,请参阅@DirtiesContext 在 Spring Testing Annotations 和@DirtiesContext javadoc 的文档。 |
3.5.7. 测试夹具的依赖注入
当您使用DependencyInjectionTestExecutionListener
(由默认配置),测试实例的依赖项是从应用程序上下文中注入的应用程序上下文@ContextConfiguration
或相关 附注。 您可以使用 setter 注入、字段注入或两者兼而有之,具体取决于您选择哪些注释以及是否将它们放在 setter 方法或字段上。如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入(请参阅依赖注入SpringExtension
). 为了与 Spring 基于注释的注入支持保持一致,您还可以使用 Spring 的@Autowired
注释或@Inject
JSR-330 的字段和 setter 注入的注释。
对于 JUnit Jupiter 以外的测试框架,TestContext 框架不会
参与测试类的实例化。因此,使用@Autowired 或@Inject for 构造函数对测试类没有影响。 |
尽管在生产代码中不鼓励字段注入,但字段注入是
实际上在测试代码中很自然。差异的基本原理是,您将
切勿直接实例化测试类。因此,没有必要能够
调用public 构造函数或 setter 方法。 |
因为@Autowired
用于通过以下方式执行自动布线
类型,如果你有多个相同类型的 Bean 定义,你不能依赖它
方法。在这种情况下,您可以使用@Autowired
在
结合@Qualifier
.您也可以选择使用@Inject
结合@Named
.或者,如果您的测试类可以访问其ApplicationContext
你
可以通过使用(例如)调用applicationContext.getBean("titleRepository", TitleRepository.class)
.
如果您不希望将依赖项注入应用于测试实例,请不要注释
fields 或 setter 方法与@Autowired
或@Inject
.或者,您可以禁用
通过显式配置您的类来完全注入依赖项@TestExecutionListeners
并省略DependencyInjectionTestExecutionListener.class
从监听器列表中。
考虑测试HibernateTitleRepository
类,如目标部分所述。接下来的两个代码列表演示了
使用@Autowired
on 字段和 setter 方法。应用程序上下文配置
在所有示例代码列表之后显示。
以下代码列表中的依赖注入行为并非特定于 JUnit 木星。相同的 DI 技术可以与任何受支持的测试结合使用 框架。 以下示例调用静态断言方法,例如 |
第一个代码列表显示了基于 JUnit Jupiter 的测试类实现,该测试类
使用@Autowired
用于现场注射:
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
@Autowired
HibernateTitleRepository titleRepository;
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
@Autowired
lateinit var titleRepository: HibernateTitleRepository
@Test
fun findById() {
val title = titleRepository.findById(10)
assertNotNull(title)
}
}
或者,您可以将类配置为使用@Autowired
对于入孵机注射,如
遵循:
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
HibernateTitleRepository titleRepository;
@Autowired
void setTitleRepository(HibernateTitleRepository titleRepository) {
this.titleRepository = titleRepository;
}
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
lateinit var titleRepository: HibernateTitleRepository
@Autowired
fun setTitleRepository(titleRepository: HibernateTitleRepository) {
this.titleRepository = titleRepository
}
@Test
fun findById() {
val title = titleRepository.findById(10)
assertNotNull(title)
}
}
前面的代码列表使用与@ContextConfiguration
注释(即,repository-config.xml
).以下内容
显示此配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
<bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- configuration elided for brevity -->
</bean>
</beans>
如果您从 Spring 提供的测试基类扩展,该基类恰好使用 Java
Kotlin
指定的限定符值表示特定的 |
3.5.8. 测试请求范围和会话范围的 Bean
Spring 支持请求范围和会话范围 bean 的 bean,你可以测试你的请求范围和会话范围 bean 的 bean,请按照以下步骤作:
-
确保
WebApplicationContext
通过注释测试来加载测试 类与@WebAppConfiguration
. -
将模拟请求或会话注入测试实例并准备测试 适当的夹具。
-
调用从配置的
WebApplicationContext
(带有依赖注入)。 -
对模拟执行断言。
下一个代码片段显示了登录用例的 XML 配置。请注意,userService
bean 依赖于请求范围的loginAction
豆。此外,LoginAction
通过使用 SpEL 表达式实例化,其中
从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望
通过 TestContext 框架管理的模拟配置这些请求参数。
以下列表显示了此用例的配置:
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:loginAction-ref="loginAction"/>
<bean id="loginAction" class="com.example.LoginAction"
c:username="#{request.getParameter('user')}"
c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy/>
</bean>
</beans>
在RequestScopedBeanTests
,我们将UserService
(即,下面的主语
test)和MockHttpServletRequest
到我们的测试实例中。在我们的requestScope()
test 方法,我们通过在
提供的MockHttpServletRequest
.当loginUser()
方法被调用在我们的userService
,我们确信用户服务可以访问请求范围的loginAction
对于当前MockHttpServletRequest
(也就是说,我们只是
设置参数)。然后,我们可以根据已知的
输入用户名和密码。以下列表显示了如何执行此作:
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpServletRequest request;
@Test
void requestScope() {
request.setParameter("user", "enigma");
request.setParameter("pswd", "$pr!ng");
LoginResults results = userService.loginUser();
// assert results
}
}
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired lateinit var userService: UserService
@Autowired lateinit var request: MockHttpServletRequest
@Test
fun requestScope() {
request.setParameter("user", "enigma")
request.setParameter("pswd", "\$pr!ng")
val results = userService.loginUser()
// assert results
}
}
以下代码片段类似于我们之前看到的请求范围的代码片段
豆。然而,这一次,userService
bean 依赖于 session-scopeduserPreferences
豆。请注意,UserPreferences
bean 是通过使用
从当前 HTTP 会话中检索主题的 SpEL 表达式。在我们的测试中,我们
需要在 TestContext 框架管理的模拟会话中配置主题。这
以下示例显示了如何执行此作:
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:userPreferences-ref="userPreferences" />
<bean id="userPreferences" class="com.example.UserPreferences"
c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy/>
</bean>
</beans>
在SessionScopedBeanTests
,我们将UserService
和MockHttpSession
到
我们的测试实例。在我们的sessionScope()
测试方法,我们通过以下方式设置我们的测试夹具
设置预期的theme
属性MockHttpSession
.当processUserPreferences()
方法被调用在我们的userService
,我们确信
用户服务有权访问会话范围的userPreferences
对于当前MockHttpSession
,我们可以根据
配置的主题。以下示例显示了如何执行此作:
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpSession session;
@Test
void sessionScope() throws Exception {
session.setAttribute("theme", "blue");
Results results = userService.processUserPreferences();
// assert results
}
}
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired lateinit var userService: UserService
@Autowired lateinit var session: MockHttpSession
@Test
fun sessionScope() {
session.setAttribute("theme", "blue")
val results = userService.processUserPreferences()
// assert results
}
}
3.5.9. 事务管理
在 TestContext 框架中,事务由TransactionalTestExecutionListener
,默认情况下配置,即使您没有
显式声明@TestExecutionListeners
在测试类上。启用对
事务,但是,您必须配置PlatformTransactionManager
bean 中的ApplicationContext
加载了@ContextConfiguration
语义(进一步
详细信息将在后面提供)。此外,您必须声明 Spring 的@Transactional
在类或方法级别进行测试的注释。
测试管理的事务
测试管理的事务是通过使用TransactionalTestExecutionListener
或以编程方式使用TestTransaction
(后述)。您不应将此类事务与 Spring 管理的事务混淆
事务(由 Spring 在ApplicationContext
加载
测试)或应用程序管理的事务(在
测试调用的应用程序代码)。Spring 管理和应用程序管理
事务通常参与测试管理的事务。但是,您应该使用
如果 Spring 管理或应用程序管理的事务配置了任何
传播类型以外的REQUIRED
或SUPPORTS
(有关详细信息,请参阅有关事务传播的讨论)。
抢占超时和测试管理的事务
使用测试框架中任何形式的抢占式超时时必须小心 与 Spring 的测试管理事务结合使用。 具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过 一个 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
使用@Transactional
导致测试在
事务,默认情况下,在测试完成后自动回滚。
如果测试类的注释为@Transactional
,则该类中的每个测试方法
层次结构在事务中运行。未注释的测试方法@Transactional
(在类或方法级别)不在事务中运行。注意
那@Transactional
测试生命周期方法(例如,方法
用 JUnit Jupiter 的@BeforeAll
,@BeforeEach
等。此外,测试
用@Transactional
但有propagation
属性设置为NOT_SUPPORTED
或NEVER
不在事务中运行。
属性 | 支持测试管理的事务 |
---|---|
|
是的 |
|
只 |
|
不 |
|
不 |
|
不 |
|
否:使用 |
|
否:使用 |
方法级生命周期方法——例如,用 JUnit Jupiter 的 如果您需要在
transaction,不妨注入一个对应的 |
请注意AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
在类级别预先配置为事务支持。
以下示例演示了编写集成测试的常见场景
基于休眠的UserRepository
:
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
lateinit var repository: HibernateUserRepository
@Autowired
lateinit var sessionFactory: SessionFactory
lateinit var jdbcTemplate: JdbcTemplate
@Autowired
fun setDataSource(dataSource: DataSource) {
this.jdbcTemplate = JdbcTemplate(dataSource)
}
@Test
fun createUser() {
// track initial state in test database:
val count = countRowsInTable("user")
val user = User()
repository.save(user)
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush()
assertNumUsers(count + 1)
}
private fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
private fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
如事务回滚和提交行为中所述,无需
清理createUser()
方法运行,因为对
数据库由TransactionalTestExecutionListener
.
事务回滚和提交行为
默认情况下,测试事务将在完成
测试;但是,事务提交和回滚行为可以声明性地配置
通过@Commit
和@Rollback
附注。有关更多详细信息,请参阅注释支持部分中的相应条目。
程序化事务管理
您可以使用静态
方法TestTransaction
.例如,您可以使用TestTransaction
测试中
方法、before methods 和 after 方法来启动或结束当前测试管理
事务或配置当前测试管理事务以进行回滚或提交。
对TestTransaction
只要TransactionalTestExecutionListener
已启用。
以下示例演示了TestTransaction
.请参阅
javadoc 的TestTransaction
了解更多详情。
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// assert initial state in test database:
assertNumUsers(2);
deleteFromTables("user");
// changes to the database will be committed!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun transactionalTest() {
// assert initial state in test database:
assertNumUsers(2)
deleteFromTables("user")
// changes to the database will be committed!
TestTransaction.flagForCommit()
TestTransaction.end()
assertFalse(TestTransaction.isActive())
assertNumUsers(0)
TestTransaction.start()
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
在事务之外运行代码
有时,可能需要在事务测试之前或之后运行某些代码
方法,但要在事务上下文之外——例如,验证初始
运行测试之前的数据库状态或验证预期的事务提交
测试运行后的行为(如果测试配置为提交事务)。TransactionalTestExecutionListener
支持@BeforeTransaction
和@AfterTransaction
注解。您可以注释任何void
测试类中的方法或任何void
默认方法,在测试接口中使用其中之一
注释,以及TransactionalTestExecutionListener
确保您在
transaction 方法或 after transaction 方法在适当的时间运行。
任何之前的方法(例如用 JUnit Jupiter 的@BeforeEach )
以及任何 after 方法(例如用 JUnit Jupiter 的@AfterEach ) 是
在事务中运行。此外,用@BeforeTransaction 或@AfterTransaction 对于未配置为在
交易。 |
配置事务管理器
TransactionalTestExecutionListener
期望PlatformTransactionManager
豆子要成为
在 Spring 中定义ApplicationContext
用于测试。如果有多个实例
之PlatformTransactionManager
在测试的ApplicationContext
,您可以声明一个
限定符,使用@Transactional("myTxMgr")
或@Transactional(transactionManager =
"myTxMgr")
或TransactionManagementConfigurer
可由@Configuration
类。请查阅Java文档
为TestContextTransactionUtils.retrieveTransactionManager()
有关
用于在测试的ApplicationContext
.
所有与事务相关的注释的演示
以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试
突出显示所有与事务相关的注释的方案。该示例不是有意的
演示最佳实践,而是演示这些注释如何
使用。有关进一步的信息,请参阅注释支持部分
信息和配置示例。事务管理@Sql
包含一个使用@Sql
为
具有默认事务回滚语义的声明性 SQL 脚本执行。这
以下示例显示了相关注释:
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
fun modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
fun tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
测试 ORM 代码时避免误报
当您测试作 Hibernate 会话或 JPA 状态的应用程序代码时 持久性上下文,请确保刷新测试方法中的基础工作单元 运行该代码。未能刷新基础工作单元可能会产生错误 positives:您的测试通过了,但相同的代码在实时生产中抛出异常 环境。请注意,这适用于任何维护内存单元的 ORM 框架 工作。在以下基于 Hibernate 的示例测试用例中,一种方法演示了 误报,另一种方法正确暴露刷新 会期: Java
Kotlin
以下示例显示了 JPA 的匹配方法: Java
Kotlin
|
测试 ORM 实体生命周期回调
与测试 ORM 代码时避免误报的说明类似,如果您的应用程序使用实体生命周期回调( 称为实体侦听器),请确保刷新测试中的基础工作单元 运行该代码的方法。未能刷新或清除基础工作单元可能会 导致某些生命周期回调未被调用。 例如,当使用 JPA 时, 以下示例演示如何刷新 Java
Kotlin
有关使用所有JPA生命周期回调的工作示例,请参阅Spring Framework测试套件中的JpaEntityListenerTests。 |
3.5.10. 执行 SQL 脚本
在针对关系数据库编写集成测试时,通常有利于
运行 SQL 脚本以修改数据库模式或将测试数据插入表中。这spring-jdbc
模块支持初始化嵌入式或现有数据库
通过在 SpringApplicationContext
已加载。请参阅嵌入式数据库支持和使用
嵌入式数据库了解详情。
虽然初始化数据库进行一次测试非常有用,但当ApplicationContext
加载时,有时必须能够修改
集成测试期间的数据库。以下部分介绍如何运行 SQL
在集成测试期间以编程和声明方式编写脚本。
以编程方式执行 SQL 脚本
Spring 提供了以下选项,用于在 集成测试方法。
-
org.springframework.jdbc.datasource.init.ScriptUtils
-
org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
-
org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
-
org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils
提供了用于处理 SQL 的静态实用程序方法的集合
脚本,主要供框架内内部使用。但是,如果您
需要完全控制 SQL 脚本的解析和运行方式,ScriptUtils
可能适合
您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc
方法ScriptUtils
了解更多详情。
ResourceDatabasePopulator
提供了一个基于对象的 API,用于以编程方式填充,
使用外部
资源。ResourceDatabasePopulator
提供配置字符的选项
编码、语句分隔符、注释分隔符和错误处理标志
解析和运行脚本。每个配置选项都有一个合理的
默认值。请参阅 javadoc
有关默认值的详细信息。要运行在ResourceDatabasePopulator
,您可以调用populate(Connection)
method 设置为
针对java.sql.Connection
或execute(DataSource)
方法
以针对javax.sql.DataSource
.以下示例
为测试模式和测试数据指定 SQL 脚本,将语句分隔符设置为 ,并针对@@
DataSource
:
@Test
void databaseTest() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(
new ClassPathResource("test-schema.sql"),
new ClassPathResource("test-data.sql"));
populator.setSeparator("@@");
populator.execute(this.dataSource);
// run code that uses the test schema and data
}
@Test
fun databaseTest() {
val populator = ResourceDatabasePopulator()
populator.addScripts(
ClassPathResource("test-schema.sql"),
ClassPathResource("test-data.sql"))
populator.setSeparator("@@")
populator.execute(dataSource)
// run code that uses the test schema and data
}
请注意ResourceDatabasePopulator
内部委托给ScriptUtils
用于解析
以及运行 SQL 脚本。同样,executeSqlScript(..)
方法AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
在内部使用ResourceDatabasePopulator
运行 SQL 脚本。请参阅 Javadoc 中的
各种executeSqlScript(..)
方法了解更多详情。
使用 @Sql 以声明方式执行 SQL 脚本
除了上述以编程方式运行 SQL 脚本的机制之外,
您可以在 Spring TestContext 框架中以声明方式配置 SQL 脚本。
具体来说,您可以声明@Sql
将测试类或测试方法上的注释设置为
配置单个 SQL 语句或 SQL 脚本的资源路径,这些
在集成测试方法之前或之后针对给定数据库运行。对@Sql
由SqlScriptsTestExecutionListener
,默认启用。
方法级@Sql 默认情况下,声明会覆盖类级声明。如
但是,此行为可以按测试类或每个测试类配置
测试方法@SqlMergeMode .看合并和覆盖配置@SqlMergeMode 了解更多详情。 |
路径资源语义
每条路径都被解释为一个弹簧Resource
. 普通路径(例如"schema.sql"
) 被视为相对于
定义了测试类。以斜杠开头的路径被视为绝对路径
classpath 资源(例如"/org/example/schema.sql"
).引用
URL(例如,以classpath:
,file:
,http:
) 通过使用
指定的资源协议。
以下示例演示如何使用@Sql
在类级别和方法级别
在基于 JUnit Jupiter 的集成测试类中:
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that uses the test schema and test data
}
}
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
fun emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-schema.sql", "/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
}
默认脚本检测
如果未指定 SQL 脚本或语句,则会尝试检测default
脚本,取决于@Sql
被声明。如果无法检测到默认值,则IllegalStateException
被抛出。
-
类级声明:如果带注释的测试类是
com.example.MyTest
这 对应的默认脚本是classpath:com/example/MyTest.sql
. -
方法级声明:如果带注释的测试方法名为
testMethod()
并且是在类中定义com.example.MyTest
,对应的默认脚本是classpath:com/example/MyTest.testMethod.sql
.
声明多个@Sql
集
如果您需要为给定的测试类或测试配置多组 SQL 脚本方法,但具有不同的语法配置、不同的错误处理规则,或每组不同的执行阶段,您可以声明@Sql
. 跟 Java 8,您可以使用@Sql
作为可重复的注释。否则,您可以使用@SqlGroup
注释作为显式容器,用于声明@Sql
.
以下示例演示如何使用@Sql
作为 Java 8 的可重复注释:
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
在前面示例中介绍的方案中,test-schema.sql
脚本使用
单行注释的语法不同。
以下示例与前面的示例相同,不同之处在于@Sql
声明在@SqlGroup
.在 Java 8 及更高版本中,使用@SqlGroup
是可选的,但您可能需要使用@SqlGroup
用于兼容
其他 JVM 语言,例如 Kotlin。
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
@Test
@SqlGroup(
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql"))
fun userTest() {
// Run code that uses the test schema and test data
}
脚本执行阶段
默认情况下,SQL 脚本在相应的测试方法之前运行。但是,如果您需要在测试方法之后运行一组特定的脚本(例如,清理up 数据库状态),您可以使用executionPhase
属性@Sql
,作为以下示例所示:
@Test
@Sql(
scripts = "create-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
scripts = "delete-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD
)
void userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
@Test
@SqlGroup(
Sql("create-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED)),
Sql("delete-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD))
fun userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
请注意ISOLATED
和AFTER_TEST_METHOD
静态导入自Sql.TransactionMode
和Sql.ExecutionPhase
分别。
脚本配置@SqlConfig
您可以使用@SqlConfig
注解。 当在集成测试类上声明为类级注释时,@SqlConfig
用作测试类层次结构中所有 SQL 脚本的全局配置。 什么时候 直接使用config
属性的@Sql
注解@SqlConfig
用作在封闭中声明的 SQL 脚本的本地配置@Sql
注解。 中的每个属性@SqlConfig
有一个隐式默认值,即记录在相应属性的 javadoc 中。由于定义了注释属性,不幸的是,它不是可以分配一个null
添加到 annotation 属性。因此,为了支持继承的全局配置的覆盖,@SqlConfig
属性具有显式默认值为(对于字符串)、(对于数组)或""
{}
DEFAULT
(对于
枚举)。这种方法允许本地声明@SqlConfig
选择性覆盖
来自全局声明的各个属性@SqlConfig
通过提供值 other
比 、 或""
{}
DEFAULT
.全球@SqlConfig
属性在
当地@SqlConfig
属性不提供除 、 或 以外的显式值""
{}
DEFAULT
.因此,显式本地配置将覆盖全局配置。
提供的配置选项@Sql
和@SqlConfig
等同于那些
基金资助ScriptUtils
和ResourceDatabasePopulator
但是这些的超集
由<jdbc:initialize-database/>
XML 命名空间元素。请参阅
各个属性@Sql
和@SqlConfig
了解详情。
事务管理@Sql
默认情况下,SqlScriptsTestExecutionListener
推断出所需的交易
使用@Sql
.具体来说,运行 SQL 脚本
没有事务,则在现有的 Spring 管理事务(例如,一个
事务由TransactionalTestExecutionListener
对于标注为@Transactional
),或在隔离事务中,具体取决于配置的值
的transactionMode
属性@SqlConfig
并且存在PlatformTransactionManager
在测试的ApplicationContext
.作为最低限度,
但是,一个javax.sql.DataSource
必须存在于测试的ApplicationContext
.
如果SqlScriptsTestExecutionListener
检测DataSource
和PlatformTransactionManager
并推断事务语义不适合你的需求,
您可以通过设置dataSource
和transactionManager
属性@SqlConfig
.此外,您可以控制事务传播
通过将transactionMode
属性@SqlConfig
(例如,是否
脚本应在隔离事务中运行)。虽然对所有
支持的事务管理选项@Sql
超出了这个范围
参考手册,Javadoc@SqlConfig
和SqlScriptsTestExecutionListener
提供详细信息,以下示例显示了一个典型的测试场景
使用 JUnit Jupiter 和事务测试@Sql
:
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
final JdbcTemplate jdbcTemplate;
@Autowired
TransactionalSqlScriptsTests(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
@Sql("/test-data.sql")
void usersTest() {
// verify state in test database:
assertNumUsers(2);
// run code that uses the test data...
}
int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
void assertNumUsers(int expected) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.");
}
}
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {
val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)
@Test
@Sql("/test-data.sql")
fun usersTest() {
// verify state in test database:
assertNumUsers(2)
// run code that uses the test data...
}
fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
fun assertNumUsers(expected: Int) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.")
}
}
请注意,在usersTest()
方法是
run,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sql
script)由TransactionalTestExecutionListener
(请参阅事务管理
细节)。
合并和覆盖配置@SqlMergeMode
从 Spring Framework 5.2 开始,可以合并方法级@Sql
声明,并使用类级声明。例如,这允许您为每个测试类提供一次数据库架构或一些常见测试数据的配置,然后为每个测试方法提供额外的用例特定测试数据。要启用@Sql
mergeing,注释您的测试类或测试方法@SqlMergeMode(MERGE)
. 要禁用特定测试方法(或特定测试子类)的合并,您可以切换回默认模式 通过@SqlMergeMode(OVERRIDE)
. 请查阅@SqlMergeMode
注释文档部分有关示例和更多详细信息。
3.5.11. 并行测试执行
Spring Framework 5.0 引入了对在单个 JVM 中使用 Spring TestContext 框架时并行执行测试的基本支持。一般来说,这意味着大多数测试类或测试方法可以并行运行,而无需对测试代码进行任何更改或配置。
如需详细了解如何设置并行测试执行,请参阅testing 框架、构建工具或 IDE 的文档。 |
请记住,在测试套件中引入并发可能会导致意外的副作用、奇怪的运行时行为以及间歇性失败的测试或看似随机的。因此,Spring 团队提供了以下一般准则对于何时不并行运行测试。
如果测试符合以下条件,请勿并行运行测试:
-
使用 Spring Framework 的
@DirtiesContext
支持。 -
使用 Spring Boot 的
@MockBean
或@SpyBean
支持。 -
使用 JUnit 4 的
@FixMethodOrder
支持或任何测试框架功能旨在确保测试方法按特定顺序运行。 注意 但是,如果整个测试类并行运行,则不适用。 -
更改共享服务或系统(如数据库、消息代理、文件系统等)的状态。这适用于嵌入式系统和外部系统。
如果并行测试执行失败,并出现异常,指出 这可能是由于使用 |
Spring TestContext 框架中的并行测试执行只有在以下情况下才有可能底层TestContext 实现提供了一个复制构造函数,如javadoc 中TestContext . 这DefaultTestContext Spring 中使用提供了这样的构造函数。但是,如果您使用
第三方库,提供自定义TestContext 实现时,您需要
验证它是否适合并行测试执行。 |
3.5.12. TestContext 框架支持类
本节介绍支持 Spring TestContext 框架的各种类。
弹簧 JUnit 4 流轮
Spring TestContext 框架通过自定义
runner(在 JUnit 4.12 或更高版本上受支持)。通过使用@RunWith(SpringJUnit4ClassRunner.class)
或较短的@RunWith(SpringRunner.class)
变体,开发人员可以实现基于 JUnit 4 的标准单元和集成测试,以及
同时获得 TestContext 框架的好处,例如
加载应用程序上下文、测试实例的依赖注入、事务测试
方法执行,依此类推。如果要将 Spring TestContext 框架与
替代运行器(例如 JUnit 4 的Parameterized
runner)或第三方运行器
(例如MockitoJUnitRunner
),您可以选择使用 Spring 对 JUnit 规则的支持。
以下代码列表显示了将测试类配置为
使用自定义 Spring 运行Runner
:
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
在前面的示例中,@TestExecutionListeners
配置为空列表,以
禁用默认监听器,否则这将需要ApplicationContext
自
通过@ContextConfiguration
.
Spring JUnit 4 规则
这org.springframework.test.context.junit4.rules
package 提供了以下 JUnit
4 条规则(在 JUnit 4.12 或更高版本上支持):
-
SpringClassRule
-
SpringMethodRule
SpringClassRule
是 JUnitTestRule
支持 Spring 的类级功能
TestContext 框架,而SpringMethodRule
是 JUnitMethodRule
支持
Spring TestContext 框架的实例级和方法级特性。
与SpringRunner
,Spring 基于规则的 JUnit 支持具有以下优势:
独立于任何org.junit.runner.Runner
实现,因此可以是
结合现有的替代运行器(例如 JUnit 4 的Parameterized
) 或
第三方运行器(例如MockitoJUnitRunner
).
要支持 TestContext 框架的全部功能,您必须将SpringClassRule
使用SpringMethodRule
.以下示例显示了正确的方法
要在集成测试中声明这些规则,请执行以下作:
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
JUnit 4 支持类
这org.springframework.test.context.junit4
package 提供以下支持
基于 JUnit 4 的测试用例的类(在 JUnit 4.12 或更高版本上受支持):
-
AbstractJUnit4SpringContextTests
-
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是一个抽象的基测试类,它集成了
Spring TestContext 框架,具有显式ApplicationContext
在
JUnit 4 环境。当您扩展AbstractJUnit4SpringContextTests
,您可以访问protected
applicationContext
实例变量,可用于执行显式
bean 查找或测试整个上下文的状态。
AbstractTransactionalJUnit4SpringContextTests
是AbstractJUnit4SpringContextTests
这为 JDBC 添加了一些便利功能 访问。 此类需要javax.sql.DataSource
bean 和PlatformTransactionManager
bean 在ApplicationContext
. 当您 扩展AbstractTransactionalJUnit4SpringContextTests
,您可以访问protected
jdbcTemplate
实例变量,可用于运行 SQL 语句来查询 数据库。 您可以使用此类查询来确认之前和之后的数据库状态运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在与应用程序代码相同的事务范围中运行。当与ORM 工具结合使用时,请务必避免误报。正如 JDBC 测试支持中提到的,AbstractTransactionalJUnit4SpringContextTests
还提供了方便的方法
委托给JdbcTestUtils
通过使用上述jdbcTemplate
.
此外AbstractTransactionalJUnit4SpringContextTests
提供executeSqlScript(..)
方法,用于针对配置的DataSource
.
这些类方便扩展。如果您不想要测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类,使用@RunWith(SpringRunner.class) 或 Spring 的
JUnit 规则。 |
JUnit Jupiter 的 SpringExtension
Spring TestContext 框架提供了与 JUnit Jupiter 测试的完全集成
框架,在 JUnit 5 中引入。通过使用@ExtendWith(SpringExtension.class)
,您可以实现基于 Jupiter 的标准单元
和集成测试,同时获得 TestContext 框架的好处,
例如支持加载应用程序上下文、测试实例的依赖注入、
事务测试方法执行,依此类推。
此外,由于 JUnit Jupiter 中丰富的扩展 API,Spring 提供了 Spring 支持 JUnit 4 和 测试NG:
-
测试构造函数、测试方法和测试生命周期回调的依赖注入 方法。看依赖注入
SpringExtension
了解更多详情。 -
对条件的强大支持 基于 SpEL 表达式、环境变量、系统属性、 等等。请参阅文档
@EnabledIf
和@DisabledIf
在 Spring JUnit Jupiter Testing Annotations 中了解更多详细信息和示例。 -
自定义组合的注释,结合了来自 Spring 和 JUnit Jupiter 的注释。看 这
@TransactionalDevTestConfig
和@TransactionalIntegrationTest
测试的元注释支持中的示例以了解更多详细信息。
以下代码列表显示了如何配置测试类以使用SpringExtension
结合@ContextConfiguration
:
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension::class)
// Instructs Spring to load an ApplicationContext from TestConfig::class
@ContextConfiguration(classes = [TestConfig::class])
class SimpleTests {
@Test
fun testMethod() {
// test logic...
}
}
由于您也可以在 JUnit 5 中使用注释作为元注释,因此 Spring 提供了@SpringJUnitConfig
和@SpringJUnitWebConfig
组合注释以简化
测试的配置ApplicationContext
和 JUnit Jupiter。
以下示例使用@SpringJUnitConfig
减少配置量
在前面的例子中使用:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig::class)
class SimpleTests {
@Test
fun testMethod() {
// test logic...
}
}
同样,以下示例使用@SpringJUnitWebConfig
创建WebApplicationContext
与 JUnit Jupiter 一起使用:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig::class
@SpringJUnitWebConfig(TestWebConfig::class)
class SimpleWebTests {
@Test
fun testMethod() {
// test logic...
}
}
请参阅文档@SpringJUnitConfig
和@SpringJUnitWebConfig
在 Spring JUnit Jupiter Testing Annotations 中了解更多详细信息。
依赖注入SpringExtension
SpringExtension
实现ParameterResolver
来自 JUnit Jupiter 的扩展 API,它允许 Spring 为测试提供依赖注入
构造函数、测试方法和测试生命周期回调方法。
具体说来SpringExtension
可以从测试的ApplicationContext
转换为测试构造函数和方法,这些构造函数和方法被@BeforeAll
,@AfterAll
,@BeforeEach
,@AfterEach
,@Test
,@RepeatedTest
,@ParameterizedTest
,等。
构造函数注入
如果 JUnit Jupiter 测试类的构造函数中的特定参数类型为ApplicationContext
(或其子类型)或被注释或元注释@Autowired
,@Qualifier
或@Value
,Spring 注入该特定
参数与相应的 bean 或测试的ApplicationContext
.
如果出现以下情况,还可以将 Spring 配置为自动连接测试类构造函数的所有参数 构造函数被认为是可自动连接的。构造函数被视为 如果满足以下条件之一(按优先级排序),则可自动切换。
-
构造函数的注释为
@Autowired
. -
@TestConstructor
在测试类上存在或元存在,具有autowireMode
属性设置为ALL
. -
默认的测试构造函数自动连线模式已更改为
ALL
.
看@TestConstructor
有关使用的详细信息@TestConstructor
以及如何更改全局测试构造函数自动连线模式。
如果测试类的构造函数被认为是可自动编排的,则 Spring
承担解析构造函数中所有参数的参数的责任。
因此,没有其他ParameterResolver 在 JUnit 注册 Jupiter 可以解析
此类构造函数的参数。 |
测试类的构造函数注入不得与 JUnit 结合使用
木星的 原因是 使用 |
在下面的示例中,Spring 注入了OrderService
bean 来自ApplicationContext
加载自TestConfig.class
进入OrderServiceIntegrationTests
构造 函数。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
// tests that use the injected OrderService
}
请注意,此功能允许测试依赖项final
因此是不可变的。
如果spring.test.constructor.autowire.mode
属性是all
(参见@TestConstructor
),我们可以省略@Autowired
在上一个示例中的构造函数上,结果如下。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
// tests that use the injected OrderService
}
方法注入
如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数为
类型ApplicationContext
(或其子类型)或被注释或元注释@Autowired
,@Qualifier
或@Value
,Spring 注入该特定
参数替换为测试的ApplicationContext
.
在下面的示例中,Spring 注入了OrderService
从ApplicationContext
加载自TestConfig.class
进入deleteOrder()
测试方法:
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {
@Test
fun deleteOrder(@Autowired orderService: OrderService) {
// use orderService from the test's ApplicationContext
}
}
由于ParameterResolver
在 JUnit Jupiter 中支持,您还可以
将多个依赖项注入到单个方法中,不仅来自 Spring,而且
来自 JUnit Jupiter 本身或其他第三方扩展。
以下示例显示了如何让 Spring 和 JUnit Jupiter 注入依赖项
进入placeOrderRepeatedly()
同时测试方法。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
请注意,使用@RepeatedTest
从 JUnit Jupiter 让测试方法获得访问权限
到RepetitionInfo
.
@Nested
测试类配置
Spring TestContext 框架支持在@Nested
自 Spring Framework 5.0 以来在 JUnit Jupiter 中测试类;然而,直到Spring
Framework 5.3 类级测试配置注释不是继承自
封闭类,就像它们来自超类一样。
Spring Framework 5.3 引入了对继承测试类的一流支持
配置,并且此类配置将由
违约。从默认值更改INHERIT
mode 设置为OVERRIDE
模式,您可以注释
个人@Nested
test 类与@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
.显式@NestedTestConfiguration
声明将应用于带注释的测试类以及
它的任何子类和嵌套类。因此,您可以注释顶级测试类
跟@NestedTestConfiguration
,这将适用于其所有嵌套测试类
递 归。
为了允许开发团队将默认值更改为OVERRIDE
–例如
为了与 Spring Framework 5.0 到 5.2 兼容 – 可以更改默认模式
通过 JVM 系统属性或spring.properties
文件在
类路径。请参阅“更改
默认封闭配置继承模式“注意。
虽然下面的“Hello World”示例非常简单,但它展示了如何声明
顶级类上的通用配置,该类由其继承@Nested
测试
类。在此特定示例中,只有TestConfig
配置类为
继承。每个嵌套测试类都提供自己的一组活动配置文件,从而产生
不同ApplicationContext
对于每个嵌套测试类(有关详细信息,请参阅上下文缓存)。请参阅支持的注释列表以查看
哪些注解可以继承@Nested
测试类。
@SpringJUnitConfig(TestConfig.class)
class GreetingServiceTests {
@Nested
@ActiveProfiles("lang_en")
class EnglishGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hello World");
}
}
@Nested
@ActiveProfiles("lang_de")
class GermanGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
}
}
}
@SpringJUnitConfig(TestConfig::class)
class GreetingServiceTests {
@Nested
@ActiveProfiles("lang_en")
inner class EnglishGreetings {
@Test
fun hello(@Autowired service:GreetingService) {
assertThat(service.greetWorld()).isEqualTo("Hello World")
}
}
@Nested
@ActiveProfiles("lang_de")
inner class GermanGreetings {
@Test
fun hello(@Autowired service:GreetingService) {
assertThat(service.greetWorld()).isEqualTo("Hallo Welt")
}
}
}
TestNG 支持类
这org.springframework.test.context.testng
package 提供以下支持
基于 TestNG 的测试用例的类:
-
AbstractTestNGSpringContextTests
-
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
是一个抽象的基测试类,它集成了
Spring TestContext 框架,具有显式ApplicationContext
在
TestNG 环境。当您扩展AbstractTestNGSpringContextTests
,您可以访问protected
applicationContext
实例变量,可用于执行显式
bean 查找或测试整个上下文的状态。
AbstractTransactionalTestNGSpringContextTests
是AbstractTestNGSpringContextTests
这为 JDBC 添加了一些便利功能 访问。 此类需要javax.sql.DataSource
bean 和PlatformTransactionManager
bean 在ApplicationContext
. 当您 扩展AbstractTransactionalTestNGSpringContextTests
,您可以访问protected
jdbcTemplate
实例变量,可用于运行 SQL 语句来查询 数据库。 您可以使用此类查询来确认之前和之后的数据库状态运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在与应用程序代码相同的事务范围中运行。当与ORM 工具结合使用时,请务必避免误报。正如 JDBC 测试支持中提到的,AbstractTransactionalTestNGSpringContextTests
还提供了方便的方法
委托给JdbcTestUtils
通过使用上述jdbcTemplate
.
此外AbstractTransactionalTestNGSpringContextTests
提供executeSqlScript(..)
方法,用于针对配置的DataSource
.
这些类方便扩展。如果您不想要测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类,使用@ContextConfiguration ,@TestExecutionListeners ,依此类推
使用TestContextManager .查看源代码
之AbstractTestNGSpringContextTests 了解如何检测测试类的示例。 |
3.6. Web测试客户端
WebTestClient
是专为测试服务器应用程序而设计的 HTTP 客户端。它包裹着
Spring 的 WebClient 并使用它来执行请求
但暴露了用于验证响应的测试外观。WebTestClient
可用于
执行端到端 HTTP 测试。它还可用于测试 Spring MVC 和 Spring WebFlux
没有运行服务器的应用程序通过模拟服务器请求和响应对象。
Kotlin 用户:请参阅此部分,了解如何使用WebTestClient . |
3.6.1. 设置
要设置WebTestClient
您需要选择要绑定到的服务器设置。这可以是一个
多个模拟服务器设置选项或与实时服务器的连接。
绑定到控制器
此设置允许您通过模拟请求和响应对象测试特定控制器, 没有正在运行的服务器。
对于 WebFlux 应用程序,请使用以下内容加载与 WebFlux Java 配置等效的基础设施,注册给定的 controller(s),并创建一个 WebHandler 链来处理请求:
WebTestClient client =
WebTestClient.bindToController(new TestController()).build();
val client = WebTestClient.bindToController(TestController()).build()
对于 Spring MVC,请使用以下委托给 StandaloneMockMvcBuilder 来加载与 WebMvc Java 配置等效的基础设施, 注册给定的控制器,并创建一个 MockMvc 实例来处理请求:
WebTestClient client =
MockMvcWebTestClient.bindToController(new TestController()).build();
val client = MockMvcWebTestClient.bindToController(TestController()).build()
绑定到ApplicationContext
此设置允许您使用 Spring MVC 或 Spring WebFlux 加载 Spring 配置基础设施和控制器声明,并使用它通过模拟请求处理请求和响应对象,而无需运行服务器。
对于 WebFlux,请在 SpringApplicationContext
传递给 WebHttpHandlerBuilder 以创建要处理的 WebHandler 链 请求:
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {
WebTestClient client;
@BeforeEach
void setUp(ApplicationContext context) { (2)
client = WebTestClient.bindToApplicationContext(context).build(); (3)
}
}
1 | 指定要加载的配置 |
2 | 注入配置 |
3 | 创建WebTestClient |
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {
lateinit var client: WebTestClient
@BeforeEach
fun setUp(context: ApplicationContext) { (2)
client = WebTestClient.bindToApplicationContext(context).build() (3)
}
}
1 | 指定要加载的配置 |
2 | 注入配置 |
3 | 创建WebTestClient |
对于 Spring MVC,请使用以下命令,其中 SpringApplicationContext
传递给 MockMvcBuilders.webAppContextSetup 以创建一个 MockMvc 实例来处理
请求:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
@ContextConfiguration(classes = RootConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class MyTests {
@Autowired
WebApplicationContext wac; (2)
WebTestClient client;
@BeforeEach
void setUp() {
client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); (3)
}
}
1 | 指定要加载的配置 |
2 | 注入配置 |
3 | 创建WebTestClient |
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
@ContextConfiguration(classes = RootConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class MyTests {
@Autowired
lateinit var wac: WebApplicationContext; (2)
lateinit var client: WebTestClient
@BeforeEach
fun setUp() { (2)
client = MockMvcWebTestClient.bindToApplicationContext(wac).build() (3)
}
}
1 | 指定要加载的配置 |
2 | 注入配置 |
3 | 创建WebTestClient |
绑定到路由器功能
此设置允许您通过 模拟请求和响应对象,无需运行服务器。
对于 WebFlux,请使用以下委托RouterFunctions.toWebHandler
自
创建服务器设置来处理请求:
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()
对于 Spring MVC,目前没有测试 WebMvc 功能端点的选项。
绑定到服务器
此设置连接到正在运行的服务器以执行完整的端到端 HTTP 测试:
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
客户端配置
除了前面描述的服务器设置选项外,您还可以配置客户端
选项,包括基本 URL、默认标头、客户端过滤器等。这些选项
以下内容随时可用bindToServer()
.对于所有其他配置选项,
您需要使用configureClient()
从服务器配置过渡到客户端配置,作为
遵循:
client = WebTestClient.bindToController(new TestController())
.configureClient()
.baseUrl("/test")
.build();
client = WebTestClient.bindToController(TestController())
.configureClient()
.baseUrl("/test")
.build()
3.6.2. 编写测试
WebTestClient
提供与 WebClient 相同的 API,直到使用exchange()
.有关如何
准备包含任何内容的请求,包括表单数据、多部分数据等。
调用exchange()
,WebTestClient
与WebClient
和
而是继续使用工作流来验证响应。
若要断言响应状态和标头,请使用以下命令:
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON);
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
如果您希望即使其中一个期望失败,也能断言所有期望,您可以 用expectAll(..)
而不是多个链式的expect*(..)
调用。 此功能是类似于 AssertJ 中的软断言支持和assertAll()
支持JUnit Jupiter。
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectAll(
spec -> spec.expectStatus().isOk(),
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
);
然后,您可以选择通过以下方法之一解码响应正文:
-
expectBody(Class<T>)
:解码为单个对象。 -
expectBodyList(Class<T>)
:解码和收集对象以List<T>
. -
expectBody()
:解码为byte[]
对于 JSON 内容或空正文。
并对生成的更高级别的对象执行断言:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).hasSize(3).contains(person);
import org.springframework.test.web.reactive.server.expectBodyList
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList<Person>().hasSize(3).contains(person)
如果内置断言不足,您可以改用对象并执行任何其他断言:
import org.springframework.test.web.reactive.server.expectBody
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (e.g. AssertJ)...
});
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody<Person>()
.consumeWith {
// custom assertions (e.g. AssertJ)...
}
或者,您可以退出工作流并获取EntityExchangeResult
:
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
import org.springframework.test.web.reactive.server.expectBody
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk
.expectBody<Person>()
.returnResult()
当您需要解码为具有泛型的目标类型时,请查找重载方法接受ParameterizedTypeReference 而不是Class<T> . |
暂无内容
如果响应不需要包含内容,您可以按如下方式断言:
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
client.post().uri("/persons")
.bodyValue(person)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty()
如果要忽略响应内容,以下内容将释放不任何断言的内容:
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound
.expectBody<Unit>()
JSON 内容
您可以使用expectBody()
没有目标类型来对原始
内容,而不是通过更高级别的对象。
要使用 JSONAssert 验证完整的 JSON 内容,请执行以下作:
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
要使用 JSONPath 验证 JSON 内容,请执行以下作:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason")
流式响应
要测试潜在的无限流,例如"text/event-stream"
或"application/x-ndjson"
,首先验证响应状态和标头,然后
获取一个FluxExchangeResult
:
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult(MyEvent.class);
import org.springframework.test.web.reactive.server.returnResult
val result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult<MyEvent>()
现在,您已准备好使用StepVerifier
从reactor-test
:
Flux<Event> eventFlux = result.getResponseBody();
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith(p -> ...)
.thenCancel()
.verify();
val eventFlux = result.getResponseBody()
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith { p -> ... }
.thenCancel()
.verify()
MockMvc 断言
WebTestClient
是 HTTP 客户端,因此它只能验证客户端中的内容
响应,包括状态、标头和正文。
当使用 MockMvc 服务器设置测试 Spring MVC 应用程序时,您拥有额外的
选择对服务器响应执行进一步的断言。要做到这一点,首先要
获取ExchangeResult
断言身体后:
// For a response with a body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty();
// For a response with a body
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
// For a response without a body
val result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty();
然后切换到 MockMvc 服务器响应断言:
MockMvcWebTestClient.resultActionsFor(result)
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"));
MockMvcWebTestClient.resultActionsFor(result)
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"));
3.7. 模拟Mvc
Spring MVC Test 框架,也称为 MockMvc,为测试 Spring 提供了支持 MVC 应用程序。它执行完整的 Spring MVC 请求处理,但通过模拟请求和 response 对象,而不是正在运行的服务器。
MockMvc 可以单独用于执行请求和验证响应。它也可以是通过 WebTestClient 使用,其中 MockMvc 插入作为服务器来处理请求。的优点WebTestClient
是使用更高级别的选项对象而不是原始数据,以及切换到完整的端到端 HTTP 的能力针对实时服务器进行测试并使用相同的测试 API。
3.7.1. 概述
您可以通过实例化控制器、注入它具有依赖项并调用其方法来为 Spring MVC 编写普通单元测试。但是,此类测试不会验证请求映射、数据绑定、消息转换、类型转换、验证和它们是否涉及任何支持@InitBinder
,@ModelAttribute
或@ExceptionHandler
方法。
Spring MVC 测试框架,也称为MockMvc
,旨在提供更完整的
测试没有正在运行的服务器的 Spring MVC 控制器。它通过调用
这DispatcherServlet
并从spring-test
模块,该模块复制了完整的 Spring MVC 请求处理,而无需
正在运行的服务器。
MockMvc 是一个服务器端测试框架,可让您验证大部分功能 使用轻量级和有针对性的测试的 Spring MVC 应用程序。你可以在 它自己的来执行请求和验证响应,或者您也可以通过 将 MockMvc 插入为 Server,用于处理请求的 WebTestClient API 跟。
静态导入
直接使用 MockMvc 执行请求时,您需要静态导入:
-
MockMvcBuilders.*
-
MockMvcRequestBuilders.*
-
MockMvcResultMatchers.*
-
MockMvcResultHandlers.*
记住这一点的一种简单方法是搜索MockMvc*
.如果使用 Eclipse,请务必同时
在 Eclipse 首选项中将上述内容添加为“最喜欢的静态成员”。
通过 WebTestClient 使用 MockMvc 时,不需要静态导入。
这WebTestClient
提供流畅的 API,无需静态导入。
设置选择
MockMvc 可以通过以下两种方式之一进行设置。一种是直接指向控制器 想要测试并以编程方式配置 Spring MVC 基础设施。二是 指向包含 Spring MVC 和控制器基础设施的 Spring 配置。
要设置 MockMvc 以测试特定控制器,请使用以下命令:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
class MyWebTests {
lateinit var mockMvc : MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
}
// ...
}
或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该 WebTestClient 委托给同一构建器如上所示。
要通过 Spring 配置设置 MockMvc,请使用以下命令:
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该 WebTestClient 委托给同一构建器如上所示。
您应该使用哪个设置选项?
这webAppContextSetup
加载实际的 Spring MVC 配置,从而产生更多
完成集成测试。由于 TestContext 框架缓存加载的 Spring
配置,它有助于保持测试快速运行,即使您在
测试套件。此外,您可以通过 Spring 将模拟服务注入控制器
配置,以继续专注于测试 Web 层。以下示例声明
Mockito 的模拟服务:
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.AccountService"/>
</bean>
然后,您可以将模拟服务注入到测试中,以设置和验证您的 期望值,如以下示例所示:
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {
@Autowired
AccountService accountService;
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {
@Autowired
lateinit var accountService: AccountService
lateinit mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
这standaloneSetup
另一方面,更接近于单元测试。它测试一个
控制器。您可以手动注入具有模拟依赖项的控制器,并且
它不涉及加载 Spring 配置。此类测试更侧重于风格
并更容易查看正在测试哪个控制器,是否有任何特定的 Spring
需要 MVC 配置才能工作,依此类推。这standaloneSetup
也是一个非常
编写临时测试以验证特定行为或调试问题的便捷方法。
与大多数“集成与单元测试”的争论一样,没有对错之分
答。但是,使用standaloneSetup
确实意味着需要额外的webAppContextSetup
测试以验证您的 Spring MVC 配置。
或者,您可以使用webAppContextSetup
,以便始终
针对实际的 Spring MVC 配置进行测试。
设置功能
无论您使用哪个 MockMvc 构建器,所有MockMvcBuilder
实现提供
一些常见且非常有用的功能。例如,您可以声明Accept
标题
所有请求,并期望状态为 200,以及Content-Type
标题
响应,如下所示:
// static import of MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
此外,第三方框架(和应用程序)可以预打包设置
指令,例如MockMvcConfigurer
.Spring 框架有一个这样的
内置实现,有助于跨请求保存和重复使用 HTTP 会话。
您可以按如下方式使用它:
// static import of SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Use mockMvc to perform requests...
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
请参阅 javadocConfigurableMockMvcBuilder
获取所有 MockMvc 构建器功能的列表,或使用 IDE 探索可用选项。
执行请求
本节介绍如何单独使用 MockMvc 来执行请求和验证响应。如果通过WebTestClient
请参阅有关编写测试的相应部分。
要执行使用任何 HTTP 方法的请求,如以下示例所示:
// static import of MockMvcRequestBuilders.*
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
accept = MediaType.APPLICATION_JSON
}
您还可以执行内部使用MockMultipartHttpServletRequest
这样就不会对多部分进行实际解析
请求。相反,您必须将其设置为类似于以下示例:
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
import org.springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
file("a1", "ABC".toByteArray(charset("UTF8")))
}
可以在 URI 模板样式中指定查询参数,如以下示例所示:
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
mockMvc.get("/hotels?thing={thing}", "somewhere")
您还可以添加表示查询或表单的 Servlet 请求参数 参数,如以下示例所示:
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
import org.springframework.test.web.servlet.get
mockMvc.get("/hotels") {
param("thing", "somewhere")
}
如果应用程序代码依赖于 Servlet 请求参数并且不检查查询
字符串显式(最常见的情况),使用哪个选项并不重要。
但是请记住,URI 模板提供的查询参数是解码的
而请求参数通过param(…)
方法预计已经
被解码。
在大多数情况下,最好将上下文路径和 Servlet 路径留在
请求 URI。如果必须使用完整的请求 URI 进行测试,请务必将contextPath
和servletPath
相应地,以便请求映射正常工作,如以下示例所示
显示:
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
import org.springframework.test.web.servlet.get
mockMvc.get("/app/main/hotels/{id}") {
contextPath = "/app"
servletPath = "/main"
}
在前面的示例中,设置contextPath
和servletPath
每个执行的请求。相反,您可以设置默认请求
属性,如以下示例所示:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
上述属性会影响通过MockMvc
实例。
如果在给定请求上也指定了相同的属性,则它将覆盖默认值
价值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要,因为
必须在每个请求中指定它们。
定义期望
您可以通过附加一个或多个期望来定义期望andExpect(..)
调用后
执行请求,如以下示例所示。一旦一个期望落空,
不会断言其他期望。
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
status { isOk() }
}
您可以通过将andExpectAll(..)
执行
请求,如以下示例所示。与andExpect(..)
,andExpectAll(..)
保证所有提供的期望都将被断言,并且
所有故障都将被跟踪和报告。
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpectAll(
status().isOk(),
content().contentType("application/json;charset=UTF-8"));
MockMvcResultMatchers.*
提供了许多期望,其中一些是进一步的
嵌套了更详细的期望。
期望分为两大类。第一类断言验证 响应的属性(例如,响应状态、标头和内容)。 这些是需要断言的最重要的结果。
第二类断言超出了回应范围。这些断言可以让您 检查 Spring MVC 的特定方面,例如哪个控制器方法处理了 请求,是否引发并处理了异常,模型的内容是什么, 选择了什么视图,添加了哪些 Flash 属性,等等。他们还允许您 检查 Servlet 的特定方面,例如请求和会话属性。
以下测试断言绑定或验证失败:
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
很多时候,在编写测试时,转储执行的结果很有用
请求。您可以按如下方式执行此作,其中print()
是从MockMvcResultHandlers
:
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andDo {
print()
}.andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
只要请求处理不会导致未处理的异常,就会print()
方法
将所有可用的结果数据打印到System.out
.还有一个log()
method 和
另外两个变体print()
方法,该方法接受OutputStream
和
一个接受Writer
.例如,调用print(System.err)
打印结果
data 到System.err
,同时调用print(myWriter)
将结果数据打印到自定义
作家。如果要记录结果数据而不是打印结果数据,可以调用log()
方法,将结果数据记录为单个DEBUG
消息org.springframework.test.web.servlet.result
日志记录类别。
在某些情况下,您可能希望直接访问结果并验证以下内容
否则无法验证。这可以通过将.andReturn()
毕竟
其他期望,如以下示例所示:
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...
如果所有测试都重复相同的期望值,则可以在以下情况下设置一次共同期望值。
构建MockMvc
实例,如以下示例所示:
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
请注意,通用期望始终是应用的,如果没有
创建单独的MockMvc
实例。
当 JSON 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以使用 JsonPath 表达式验证生成的链接,如以下示例所示:
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
mockMvc.get("/people") {
accept(MediaType.APPLICATION_JSON)
}.andExpect {
jsonPath("$.links[?(@.rel == 'self')].href") {
value("http://localhost:8080/people")
}
}
当 XML 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以使用使用 XPath 表达式验证生成的链接:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
accept(MediaType.APPLICATION_XML)
}.andExpect {
xpath("/person/ns:link[@rel='self']/@href", ns) {
string("http://localhost:8080/people")
}
}
异步请求
本节介绍如何单独使用 MockMvc 来测试异步请求处理。
如果通过 WebTestClient 使用 MockMvc,则无需执行任何特殊作即可
异步请求作为WebTestClient
自动执行描述的内容
在本节中。
Spring MVC 支持的 Servlet 3.0 异步请求通过退出 Servlet 容器来工作 线程,并允许应用程序异步计算响应,之后 进行异步调度以完成 Servlet 容器线程上的处理。
在 Spring MVC Test 中,可以通过断言生成的异步值来测试异步请求
首先,然后手动执行异步调度,最后验证响应。
下面是返回DeferredResult
,Callable
,
或反应型,如反应器Mono
:
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) (1)
.andExpect(request().asyncStarted()) (2)
.andExpect(request().asyncResult("body")) (3)
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
.andExpect(status().isOk()) (5)
.andExpect(content().string("body"));
}
1 | 检查响应状态是否保持不变 |
2 | 异步处理必须已启动 |
3 | 等待并断言异步结果 |
4 | 手动执行 ASYNC 调度(因为没有正在运行的容器) |
5 | 验证最终响应 |
@Test
fun test() {
var mvcResult = mockMvc.get("/path").andExpect {
status { isOk() } (1)
request { asyncStarted() } (2)
// TODO Remove unused generic parameter
request { asyncResult<Nothing>("body") } (3)
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult)) (4)
.andExpect {
status { isOk() } (5)
content().string("body")
}
}
1 | 检查响应状态是否保持不变 |
2 | 异步处理必须已启动 |
3 | 等待并断言异步结果 |
4 | 手动执行 ASYNC 调度(因为没有正在运行的容器) |
5 | 验证最终响应 |
流式响应
您可以使用WebTestClient
以测试流式处理响应,例如服务器发送的事件。然而MockMvcWebTestClient
不支持无限
流,因为无法从客户端取消服务器流。
要测试无限流,您需要绑定到正在运行的服务器,
或者在使用 Spring Boot 时,使用正在运行的服务器进行测试。
筛选注册
设置MockMvc
实例,您可以注册一个或多个 ServletFilter
实例,如以下示例所示:
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
已注册的过滤器通过MockFilterChain
从spring-test
和
最后一个过滤器委托给DispatcherServlet
.
MockMvc 与端到端测试
MockMVc 是建立在 Servlet API 模拟实现之上的spring-test
模块,并且不依赖于正在运行的容器。因此,有
与实际
客户端和正在运行的实时服务器。
考虑这个问题的最简单方法是从空白开始MockHttpServletRequest
.
无论你添加什么,请求都会变成什么。可能会让你措手不及的事情
是默认情况下没有上下文路径;不jsessionid
饼干;无转发,
错误或异步调度;因此,没有实际的 JSP 渲染。相反
“转发”和“重定向”URL 保存在MockHttpServletResponse
并且可以
被期望断言。
这意味着,如果您使用 JSP,则可以验证请求所在的 JSP 页面
转发,但不呈现 HTML。换句话说,不会调用 JSP。注意
但是,所有其他不依赖于转发的渲染技术,例如
Thymeleaf 和 Freemarker,按预期将 HTML 呈现到响应正文。同样的道理
用于通过@ResponseBody
方法。
或者,您可以考虑从
Spring Boot 与@SpringBootTest
.请参阅 Spring Boot 参考指南。
每种方法各有利弊。Spring MVC Test 中提供的选项包括
从经典单元测试到完全集成测试的不同站点。成为
当然,春季 MVC 测试中的任何选项都不属于经典单元类别
测试,但他们离它更近了一点。例如,您可以隔离 Web 图层
通过将模拟服务注入控制器,在这种情况下,您正在测试 Web
层仅通过DispatcherServlet
但使用实际的 Spring 配置,就像你一样
可能会将数据访问层与其上方的层隔离开来测试。此外,您还可以使用
独立设置,一次专注于一个控制器并手动提供
使其正常工作所需的配置。
使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的tests 是服务器端的,因此您可以检查使用了什么处理程序,如果异常是使用 HandlerExceptionResolver 处理,模型的内容是什么,有什么绑定错误,以及其他细节。这意味着更容易编写期望,由于服务器不是一个不透明的盒子,就像通过实际的 HTTP 测试它时一样 客户。 这通常是经典单元测试的一个优势:编写更容易,推理和调试,但不能取代完全集成测试的需要。在同时,重要的是不要忽视这样一个事实,即响应是最重要的检查的重要事情。简而言之,这里有多种风格和策略的空间即使在同一个项目中也能进行测试。
更多示例
该框架自己的测试包括许多示例测试,旨在展示如何单独或通过 WebTestClient 使用 MockMvc。浏览这些示例以获取更多想法。
3.7.2. HtmlUnit 集成
MockMvc 使用不依赖于 Servlet 容器的模板技术(例如,Thymeleaf、FreeMarker 等),但它不适用于 JSP,因为它们依赖于 Servlet 容器。 |
为什么选择 HtmlUnit 集成?
我想到的最明显的问题是“我为什么需要这个?”答案是最好通过探索一个非常基本的示例应用程序来找到。假设你有一个 Spring MVC Web应用程序,它支持 CRUD作Message
对象。 该应用程序还支持分页所有消息。您将如何测试它?
使用 Spring MVC Test,我们可以轻松测试我们是否能够创建一个Message
如下:
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
@Test
fun test() {
mockMvc.post("/messages/") {
param("summary", "Spring Rocks")
param("text", "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
}
如果我们想测试允许我们创建消息的表单视图怎么办? 例如 假设我们的表单如下所示:
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
我们如何确保我们的表单生成正确的请求来创建新消息? 一个 天真的尝试可能类似于以下内容:
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='summary']") { exists() }
xpath("//textarea[@name='text']") { exists() }
}
此测试有一些明显的缺点。如果我们更新控制器以使用message
而不是text
,我们的表单测试继续通过,即使 HTML 表单与控制器不同步。为了解决这个问题,我们可以将我们的两个测试结合起来,作为 遵循:
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='$summaryParamName']") { exists() }
xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
param(summaryParamName, "Spring Rocks")
param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
这将降低我们的测试错误通过的风险,但仍然有一些 问题:
-
如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂:是 字段类型正确?字段是否已启用?等等。
-
另一个问题是,我们所做的工作是我们预期的两倍。我们必须首先 验证视图,然后我们使用刚刚验证的相同参数提交视图。 理想情况下,这可以一次完成。
-
最后,我们仍然无法解释一些事情。例如,如果表单有JavaScript 验证,我们也希望测试呢?
总体问题是测试网页不涉及单一交互。相反,它是用户如何与网页交互以及该网页如何交互的组合页面与其他资源交互。例如,表单视图的结果用作用户创建消息的输入。此外,我们的表单视图可能会使用影响页面行为的其他资源,例如 JavaScript 验证。
集成测试来拯救?
为了解决前面提到的问题,我们可以执行端到端集成测试,但这有一些缺点。考虑测试允许我们翻阅 消息。 我们可能需要以下测试:
-
我们的页面是否向用户显示通知,以指示没有结果 消息为空时可用?
-
我们的页面是否正确显示一条消息?
-
我们的页面是否正确支持分页?
要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致许多额外的挑战:
-
确保数据库中有正确的消息可能很乏味。(考虑外键 约束。
-
测试可能会变慢,因为每个测试都需要确保数据库位于 正确的状态。
-
由于我们的数据库需要处于特定状态,因此我们不能并行运行测试。
-
对自动生成的 ID、时间戳等项目执行断言可以 很难。
这些挑战并不意味着我们应该放弃端到端的集成测试 完全。相反,我们可以减少端到端集成测试的数量 重构我们的详细测试以使用运行更快、更可靠的模拟服务, 并且没有副作用。然后我们可以实现少量真正的端到端 验证简单工作流程的集成测试,以确保一切协同工作 适当地。
HtmlUnit 集成选项
当您想要将 MockMvc 与 HtmlUnit 集成时,您有多种选择:
-
MockMvc 和 HtmlUnit:如果您 想要使用原始的 HtmlUnit 库。
-
MockMvc 和 WebDriver:使用此选项可以 简化开发并在集成和端到端测试之间重用代码。
-
MockMvc 和 Geb:如果需要,请使用此选项 使用 Groovy 进行测试,简化开发,并在集成和 端到端测试。
MockMvc 和 HtmlUnit
本节介绍如何集成 MockMvc 和 HtmlUnit。如果需要,请使用此选项使用原始 HtmlUnit 库。
MockMvc 和 HtmlUnit 设置
首先,确保已包含对net.sourceforge.htmlunit:htmlunit
.为了将 HtmlUnit 与 Apache HttpComponents 一起使用
4.5+,需要使用HtmlUnit 2.18或更高版本。
我们可以轻松创建一个 HtmlUnitWebClient
通过使用MockMvcWebClientBuilder
如下:
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
这是使用MockMvcWebClientBuilder .对于高级使用,
看高深MockMvcWebClientBuilder . |
这可确保引用的任何 URLlocalhost
因为服务器被定向到我们的MockMvc
实例,无需真正的 HTTP 连接。任何其他 URL 都是
像往常一样使用网络连接请求。这使我们可以轻松测试
CDN。
MockMvc 和 HtmlUnit 用法
现在我们可以像往常一样使用 HtmlUnit,但无需部署 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息,其中包含以下内容:
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
默认上下文路径为 。或者,我们可以指定上下文路径,
如"" 高深MockMvcWebClientBuilder . |
一旦我们引用了HtmlPage
,然后我们可以填写表格并提交
创建消息,如以下示例所示:
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()
最后,我们可以验证新消息是否已成功创建。以下内容 断言使用 AssertJ 库:
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")
前面的代码在许多方面改进了我们的 MockMvc 测试。 首先,我们不再需要显式验证我们的表单,然后创建一个请求 看起来像表格。相反,我们索取表格、填写并提交,从而 显着降低开销。
另一个重要因素是 HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们也可以测试 JavaScript 在我们页面中的行为。
请参阅 HtmlUnit 文档 有关使用 HtmlUnit 的其他信息。
高深MockMvcWebClientBuilder
在到目前为止的示例中,我们使用了MockMvcWebClientBuilder
以最简单的方式
可能,通过构建一个WebClient
基于WebApplicationContext
为我们加载
Spring TestContext 框架。在以下示例中重复此方法:
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
我们还可以指定其他配置选项,如以下示例所示:
WebClient webClient;
@BeforeEach
void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
作为替代方案,我们可以通过配置MockMvc
实例,并将其提供给MockMvcWebClientBuilder
如下:
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
这更冗长,但是,通过构建WebClient
使用MockMvc
实例,我们有
MockMvc 的全部功能触手可及。
有关创建MockMvc 实例,请参阅设置选项。 |
MockMvc 和 WebDriver
在前面的部分中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API 的 API。在本节中,我们在 Selenium WebDriver 中使用其他抽象来使事情变得更加容易。
为什么选择 WebDriver 和 MockMvc?
我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver?这 Selenium WebDriver 提供了一个非常优雅的 API,让我们可以轻松组织代码。自 更好地展示它是如何工作的,我们将在本节中探讨一个示例。
尽管是 Selenium 的一部分,但 WebDriver 没有 需要 Selenium 服务器来运行您的测试。 |
假设我们需要确保正确创建消息。测试涉及发现 HTML 表单输入元素,填写它们,并做出各种断言。
这种方法会导致许多单独的测试,因为我们想要测试错误条件 也。例如,我们希望确保如果我们只填写部分 表格。如果我们填写整个表格,则应显示新创建的消息 之后。
如果其中一个字段被命名为“summary”,我们可能会有类似于 以下在我们的测试中的多个地方重复:
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
那么,如果我们将id
自smmry
?这样做将迫使我们更新所有
纳入这一变化的测试。这违反了 DRY 原则,所以我们应该
理想情况下,将此代码提取到自己的方法中,如下所示:
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
setSummary(currentPage, summary);
// ...
}
fun setSummary(currentPage:HtmlPage , summary: String) {
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
}
这样做可以确保我们在更改 UI 时不必更新所有测试。
我们甚至可以更进一步,将此逻辑放在Object
那
表示HtmlPage
我们当前处于 ON,如以下示例所示:
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
class CreateMessagePage(private val currentPage: HtmlPage) {
val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
fun <T> createMessage(summary: String, text: String): T {
setSummary(summary)
val result = submit.click()
val error = at(result)
return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
}
fun setSummary(summary: String) {
summaryInput.setValueAttribute(summary)
}
fun at(page: HtmlPage): Boolean {
return "Create Message" == page.getTitleText()
}
}
}
以前,此模式称为页面对象模式。虽然我们 当然可以使用 HtmlUnit 来做到这一点,WebDriver 提供了一些我们在 以下部分将使此模式更易于实现。
MockMvc 和 WebDriver 设置
要将 Selenium WebDriver 与 Spring MVC Test 框架一起使用,请确保您的项目
包括对org.seleniumhq.selenium:selenium-htmlunit-driver
.
我们可以轻松创建一个与 MockMvc 集成的 Selenium WebDriver,方法是使用MockMvcHtmlUnitDriverBuilder
如以下示例所示:
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
这是使用MockMvcHtmlUnitDriverBuilder .对于更高级的
用法,请参阅高深MockMvcHtmlUnitDriverBuilder . |
前面的示例可确保引用的任何 URLlocalhost
因为服务器是
直接发送给我们的MockMvc
实例,无需真正的 HTTP 连接。任何其他
像往常一样,使用网络连接请求 URL。这使我们可以轻松地测试
CDN 的使用。
MockMvc 和 WebDriver 的使用
现在我们可以像往常一样使用 WebDriver,但无需部署我们的 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息,其中包含以下内容:
CreateMessagePage page = CreateMessagePage.to(driver);
val page = CreateMessagePage.to(driver)
然后,我们可以填写表格并提交以创建消息,如下所示:
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
val viewMessagePage =
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
这通过利用页面对象模式改进了 HtmlUnit 测试的设计。正如我们在为什么选择 WebDriver 和 MockMvc?中提到的,我们可以使用页面对象模式
使用 HtmlUnit,但使用 WebDriver 要容易得多。考虑以下几点CreateMessagePage
实现:
public class CreateMessagePage
extends AbstractPage { (1)
(2)
private WebElement summary;
private WebElement text;
(3)
@FindBy(css = "input[type=submit]")
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
1 | CreateMessagePage 扩展AbstractPage .我们不讨论细节AbstractPage ,但总而言之,它包含我们所有页面的通用功能。
例如,如果我们的应用程序有一个导航栏、全局错误消息和其他
功能,我们可以将此逻辑放在共享位置。 |
2 | 我们所在的 HTML 页面的每个部分都有一个成员变量
感兴趣。这些类型WebElement .WebDriver 的PageFactory 让我们删除一个
HtmlUnit 版本中的大量代码CreateMessagePage 通过自动解析
每WebElement . 这PageFactory#initElements(WebDriver,Class<T>) 方法会自动解析每个WebElement 通过使用字段名称并查找它
通过id 或name HTML 页面中的元素。 |
3 | 我们可以使用@FindBy 注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy 注释来查找我们的提交按钮,并带有css 选择器 (input[type=submit])。 |
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)
(2)
private lateinit var summary: WebElement
private lateinit var text: WebElement
(3)
@FindBy(css = "input[type=submit]")
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
this.summary.sendKeys(summary)
text.sendKeys(details)
submit.click()
return PageFactory.initElements(driver, resultPage)
}
companion object {
fun to(driver: WebDriver): CreateMessagePage {
driver.get("http://localhost:9990/mail/messages/form")
return PageFactory.initElements(driver, CreateMessagePage::class.java)
}
}
}
1 | CreateMessagePage 扩展AbstractPage .我们不讨论细节AbstractPage ,但总而言之,它包含我们所有页面的通用功能。
例如,如果我们的应用程序有一个导航栏、全局错误消息和其他
功能,我们可以将此逻辑放在共享位置。 |
2 | 我们所在的 HTML 页面的每个部分都有一个成员变量
感兴趣。这些类型WebElement .WebDriver 的PageFactory 让我们删除一个
HtmlUnit 版本中的大量代码CreateMessagePage 通过自动解析
每WebElement . 这PageFactory#initElements(WebDriver,Class<T>) 方法会自动解析每个WebElement 通过使用字段名称并查找它
通过id 或name HTML 页面中的元素。 |
3 | 我们可以使用@FindBy 注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy 注释来查找我们的提交按钮,并带有css 选择器 (input[type=submit])。 |
最后,我们可以验证新消息是否已成功创建。以下断言使用 AssertJ 断言库:
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
我们可以看到,我们的ViewMessagePage
让我们与自定义域模型进行交互。 为 示例,它公开了一个返回Message
对象:
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
然后,我们可以在断言中使用富域对象。
最后,我们不能忘记关闭WebDriver
实例,当测试完成时, 如下:
@AfterEach
void destroy() {
if (driver != null) {
driver.close();
}
}
@AfterEach
fun destroy() {
if (driver != null) {
driver.close()
}
}
有关使用 WebDriver 的更多信息,请参阅 Selenium WebDriver 文档。
高深MockMvcHtmlUnitDriverBuilder
在到目前为止的示例中,我们使用了MockMvcHtmlUnitDriverBuilder
以最简单的方式
可能,通过构建一个WebDriver
基于WebApplicationContext
为我们加载
Spring TestContext 框架。此处重复此方法,如下所示:
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
我们还可以指定其他配置选项,如下所示:
WebDriver driver;
@BeforeEach
void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
作为替代方案,我们可以通过配置MockMvc
实例,并将其提供给MockMvcHtmlUnitDriverBuilder
如下:
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
这更冗长,但是,通过构建WebDriver
使用MockMvc
实例,我们有
MockMvc 的全部功能触手可及。
有关创建MockMvc 实例,请参阅设置选项。 |
MockMvc 和 Geb
在上一节中,我们看到了如何将 MockMvc 与 WebDriver 一起使用。在本节中,我们将 使用 Geb 使我们的测试更加 Groovy-er。
为什么选择 Geb 和 MockMvc?
Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 Web驱动程序。然而,Geb 通过处理一些 样板代码。
MockMvc 和 Geb 设置
我们可以轻松初始化一个 GebBrowser
使用 MockMvc 的 Selenium WebDriver,作为
遵循:
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
这是使用MockMvcHtmlUnitDriverBuilder .对于更高级的
用法,请参阅高深MockMvcHtmlUnitDriverBuilder . |
这确保了任何 URL 引用localhost
因为服务器被定向到我们的MockMvc
实例,无需真正的 HTTP 连接。任何其他 URL 都是
正常使用网络连接请求。这使我们可以轻松测试
CDN。
MockMvc 和 Geb 用法
现在我们可以像往常一样使用 Geb,但无需将我们的应用程序部署到 一个 Servlet 容器。例如,我们可以请求视图使用 以后:
to CreateMessagePage
然后,我们可以填写表格并提交以创建消息,如下所示:
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
任何未找到的无法识别的方法调用或属性访问或引用都是 转发到当前页面对象。这删除了我们 直接使用 WebDriver 时需要。
与直接使用 WebDriver 一样,这通过使用 Page 对象改进了 HtmlUnit 测试的设计
模式。如前所述,我们可以将页面对象模式与 HtmlUnit 一起使用,并且
WebDriver,但使用 Geb 会更容易。考虑我们新的基于 GroovyCreateMessagePage
实现:
class CreateMessagePage extends Page {
static url = 'messages/form'
static at = { assert title == 'Messages : Create'; true }
static content = {
submit { $('input[type=submit]') }
form { $('form') }
errors(required:false) { $('label.error, .alert-error')?.text() }
}
}
我们CreateMessagePage
延伸Page
.我们不讨论细节Page
,但是,在summary 中,它包含我们所有页面的通用功能。我们定义了一个 URL,其中可以找到这个页面。这让我们可以导航到该页面,如下所示:
to CreateMessagePage
我们还有一个at
关闭,确定我们是否在指定的页面。它应该 返回true
如果我们在正确的页面上。这就是为什么我们可以断言我们位于正确的页面,如下所示:
then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用断言,以便我们可以确定哪里出了问题如果我们到了错误的页面。 |
接下来,我们创建一个content
关闭,指定
页。我们可以使用 jQuery 式的导航器
API 来选择我们感兴趣的内容。
最后,我们可以验证新消息是否已成功创建,如下所示:
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
有关如何充分利用 Geb 的更多详细信息,请参阅 The Book of Geb 用户手册。
3.8. 测试客户端应用程序
您可以使用客户端测试来测试内部使用RestTemplate
.这
想法是声明预期的请求并提供“存根”响应,以便您可以
专注于单独测试代码(即不运行服务器)。以下内容
示例显示了如何执行此作:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
// Test code that uses the above RestTemplate ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())
// Test code that uses the above RestTemplate ...
mockServer.verify()
在前面的示例中,MockRestServiceServer
(客户端 REST 的中心类
tests) 配置RestTemplate
使用自定义ClientHttpRequestFactory
那
根据预期断言实际请求并返回“存根”响应。在这个
case 时,我们期望请求/greeting
并希望返回 200 响应text/plain
内容。我们可以将其他预期请求和存根响应定义为
需要。当我们定义预期请求和存根响应时,RestTemplate
可以
像往常一样在客户端代码中使用。测试结束时,mockServer.verify()
可以
用于验证所有期望是否已得到满足。
默认情况下,请求按声明预期的顺序进行。你
可以设置ignoreExpectOrder
选项,在这种情况下,所有
检查期望值(按顺序)以查找给定请求的匹配项。这意味着
允许以任何顺序提出请求。以下示例使用ignoreExpectOrder
:
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
即使默认情况下使用无序请求,每个请求也只允许运行一次。
这expect
方法提供了一个重载的变体,该变体接受ExpectedCount
指定计数范围的参数(例如,once
,manyTimes
,max
,min
,between
,依此类推)。以下示例使用times
:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
// ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())
// ...
mockServer.verify()
请注意,当ignoreExpectOrder
未设置(默认值),因此请求
是预期的,则该顺序仅适用于任何
预期请求。例如,如果“/something”需要两次,然后是
“/somewhere”三次,那么在出现之前应该有对“/something”的请求
对“/somewhere”的请求,但是,除了随后的“/something”和“/somewhere”之外,
请求可以随时提出。
作为上述所有方法的替代方法,客户端测试支持还提供了ClientHttpRequestFactory
您可以配置到RestTemplate
自
将其绑定到MockMvc
实例。这允许使用实际的服务器端处理请求
逻辑,但不运行服务器。以下示例显示了如何执行此作:
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// Test code that uses the above RestTemplate ...
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))
// Test code that uses the above RestTemplate ...
3.8.1. 静态导入
与服务器端测试一样,用于客户端测试的 Fluent API 需要一些静态 进口。 通过搜索很容易找到这些MockRest*
. Eclipse 用户应该添加MockRestRequestMatchers.*
和MockRestResponseCreators.*
如 “最喜欢的静态成员” 在 Java → 编辑器下的 Eclipse 首选项中→内容辅助→收藏夹。这允许在键入 的第一个字符后使用内容辅助静态方法名称。其他 IDE(例如 IntelliJ)可能不需要任何其他 配置。 检查静态成员是否支持代码完成。
3.8.2. 客户端 REST 测试的更多示例
Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。
4. 更多资源
有关测试的更多信息,请参阅以下资源:
-
JUnit:“一个对程序员友好的 Java 测试框架”。 由 Spring Framework 在其测试套件中使用,并在 Spring TestContext 框架中受支持。
-
TestNG:受 JUnit 启发的测试框架,增加了支持 适用于测试组、数据驱动测试、分布式测试和其他功能。支持 在 Spring TestContext 框架中
-
AssertJ:“Java 的流畅断言”, 包括对 Java 8 lambda、流和其他功能的支持。
-
模拟对象:维基百科中的文章。
-
MockObjects.com:专用于模拟对象的网站,一个 在测试驱动开发中改进代码设计的技术。
-
EasyMock:Java 库“,为 接口(以及通过类扩展的对象)通过使用 Java 的代理机制。
-
JMock:支持Java代码测试驱动开发的库 使用模拟对象。
-
DbUnit:JUnit 扩展(也可用于 Ant 和 Maven),该扩展 针对数据库驱动的项目,除其他外,将您的数据库放入 测试运行之间的已知状态。
-
Testcontainers:支持 JUnit 的 Java 库 测试,提供常见数据库的轻量级、一次性实例、Selenium Web 浏览器,或可以在 Docker 容器中运行的任何其他内容。
-
Grinder:Java 负载测试框架。
-
SpringMockK:支持 Spring Boot 使用 MockK 而不是 Mockito 用 Kotlin 编写的集成测试。