本章介绍了 Spring 对集成测试的支持和单元的最佳实践 测试。Spring 团队提倡测试驱动开发 (TDD)。Spring 团队拥有 发现正确使用控制反转 (IoC) 确实使两个单元 和集成测试更容易(因为存在 setter 方法和适当的 类的构造函数使它们更容易在测试中连接在一起,而不必这样做 设置服务定位器注册表和类似结构)。
2. 单元测试
依赖注入应该使代码对容器的依赖程度降低
与传统的 Java EE 开发保持一致。构成应用程序的 POJO 应
可在 JUnit 或 TestNG 测试中进行测试,使用运算符实例化对象,而无需 Spring 或任何其他容器。您可以使用模拟对象(与其他有价值的测试技术结合使用)来单独测试代码。
如果遵循 Spring 的架构建议,则生成的干净分层
代码库的组件化有助于简化单元测试。例如
您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,
无需在运行单元测试时访问持久性数据。new
真正的单元测试通常运行速度极快,因为没有运行时基础结构 建立。强调真正的单元测试作为开发方法的一部分可以提高 您的生产力。您可能不需要测试章节的这一部分来帮助您编写 对基于 IoC 的应用程序进行有效的单元测试。对于某些单元测试方案, 但是,Spring Framework 提供了模拟对象和测试支持类,这些类 在本章中进行了介绍。
2.1. 模拟对象
Spring 包含许多专门用于模拟的包:
2.1.1. 环境
该包包含 和 抽象的模拟实现(请参见
Bean 定义配置文件和 PropertySource
抽象)。
并且对开发很有用
对依赖于特定于环境的属性的代码进行容器外测试。org.springframework.mock.env
Environment
PropertySource
MockEnvironment
MockPropertySource
2.1.2. JNDI的
该软件包包含 JNDI 的部分实现
SPI,可用于为测试套件或独立设置简单的 JNDI 环境
应用。例如,如果 JDBC 实例绑定到同一个 JNDI
测试代码中的名称,就像在 Java EE 容器中的名称一样,您可以重用这两个应用程序代码
以及测试场景中的配置,无需修改。org.springframework.mock.jndi
DataSource
软件包中的模拟
JNDI 支持是
从 Spring Framework 5.2 开始正式弃用,取而代之的是第三
Simple-JNDI等方。org.springframework.mock.jndi
|
2.1.3. Servlet 接口
该软件包包含一组全面的
Servlet API
用于测试 Web 上下文、控制器和筛选器的模拟对象。这些
mock 对象针对的是 Spring 的 Web MVC 框架,通常更
比动态模拟对象(如 EasyMock)更方便使用
或替代 Servlet API 模拟对象(例如 MockObjects)。org.springframework.mock.web
从
Spring Framework 5.0 开始,其中的模拟对象是
基于 Servlet 4.0 API。org.springframework.mock.web |
Spring MVC Test 框架建立在模拟 Servlet API 对象之上,以提供 Spring MVC 的集成测试框架。请参阅 MockMvc。
2.1.4. Spring Web 响应式
该包包含模拟实现
用于 WebFlux 应用程序。该软件包包含一个模拟
依赖于这些模拟请求和响应对象。org.springframework.mock.http.server.reactive
ServerHttpRequest
ServerHttpResponse
org.springframework.mock.web.server
ServerWebExchange
两者都是从同一个摘要扩展而来的
基类作为特定于服务器的实现,并与其共享行为。为
例如,模拟请求一旦创建就不可变,但您可以使用该方法
from
创建修改后的实例。MockServerHttpRequest
MockServerHttpResponse
mutate()
ServerHttpRequest
为了让 mock 响应正确实现写入合约并返回
写完成句柄(即 ),默认情况下,它使用 with 来缓冲数据并使其可用于测试中的断言。
应用程序可以设置自定义写入函数(例如,测试无限流)。Mono<Void>
Flux
cache().then()
WebTestClient 建立在模拟请求和响应之上,以支持 在没有 HTTP 服务器的情况下测试 WebFlux 应用程序。该客户端还可用于 使用正在运行的服务器进行端到端测试。
2.2. 单元测试支持类
Spring 包含许多可以帮助进行单元测试的类。它们分为两部分 类别:
2.2.1. 通用测试实用程序
该软件包包含多个通用实用程序
用于单元测试和集成测试。org.springframework.test.util
AopTestUtils
是
与 AOP 相关的实用程序方法。您可以使用这些方法来获取对
隐藏在一个或多个 Spring 代理后面的底层目标对象。例如,如果你
已使用 EasyMock 或 Mockito 等库将 Bean 配置为动态模拟,
并且 mock 封装在 Spring 代理中,您可能需要直接访问底层
mock 配置对它的期望并执行验证。对于 Spring 的核心 AOP
实用程序,请参阅 AopUtils 和 AopProxyUtils
。
ReflectionTestUtils
是一个
基于反射的实用方法的集合。您可以在测试中使用这些方法
需要更改常量值、设置非字段、
调用非 setter 方法,或调用非配置或生命周期
在测试应用程序代码时的回调方法,例如:public
public
public
-
ORM 框架(比如 JPA 和 Hibernate)宽恕或字段 访问,而不是域实体中属性的 setter 方法。
private
protected
public
-
Spring 对注解的支持(例如 、 和 ), 为 OR 字段、setter 方法、 和配置方法。
@Autowired
@Inject
@Resource
private
protected
-
使用注解,例如 和 用于生命周期回调 方法。
@PostConstruct
@PreDestroy
TestSocketUtils
是一个简单的
用于查找可用 TCP 端口以用于集成测试的实用程序
场景。localhost
|
2.2.2. Spring MVC测试实用程序
该包包含 ModelAndViewAssert
,您可以将其
可以与 JUnit、TestNG 或任何其他测试框架结合使用以进行单元测试
处理 Spring MVC
对象。org.springframework.test.web
ModelAndView
对 Spring MVC
控制器进行单元测试
要将 Spring MVC 类作为
POJO 进行单元测试,请结合使用 Spring 的 Servlet API 模拟中的 、 等。用于对
Spring MVC 和 REST 类与 Spring MVC 的配置结合使用,请改用 Spring MVC 测试框架。Controller ModelAndViewAssert MockHttpServletRequest MockHttpSession Controller WebApplicationContext
|
3. 集成测试
本节(本章其余大部分内容)介绍了 Spring 的集成测试 应用。它包括以下主题:
3.1. 概述
重要的是能够在不需要的情况下执行一些集成测试 部署到应用程序服务器或连接到其他企业基础架构。 这样做可以测试以下内容:
-
Spring IoC 容器上下文的正确连接。
-
使用 JDBC 或 ORM 工具进行数据访问。这可以包括正确性等内容 SQL 语句、Hibernate 查询、JPA 实体映射等。
Spring Framework
为模块中的集成测试提供了一流的支持。实际 JAR 文件的名称可能包含发行版本
也可能是长形式,具体取决于您获得的位置
它来自(有关说明,请参阅依赖项管理部分)。该库包含以下包,该包
包含用于与 Spring 容器集成测试的有价值的类。此测试
不依赖于应用程序服务器或其他部署环境。此类测试是
运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者
依赖于部署到应用程序服务器的远程测试。spring-test
org.springframework.test
org.springframework.test
单元和集成测试支持以注解驱动的 Spring TestContext Framework 的形式提供。TestContext 框架是 与正在使用的实际测试框架无关,这允许对测试进行检测 在各种环境中,包括 JUnit、TestNG 等。
3.2. 集成测试的目标
Spring 的集成测试支持具有以下主要目标:
-
在测试之间管理 Spring IoC 容器缓存。
-
提供测试夹具实例的依赖注入。
-
提供适合集成测试的事务管理。
-
提供特定于 Spring 的基类,以协助 开发人员编写集成测试。
接下来的几节将介绍每个目标,并提供实现和 配置详细信息。
3.2.1. 上下文管理和缓存
Spring TestContext 框架提供了
Spring 实例和实例的一致加载以及缓存
这些背景。支持缓存加载的上下文非常重要,因为
启动时间可能会成为一个问题——不是因为 Spring 本身的开销,而是因为 Spring 本身的开销,而是
因为 Spring 容器实例化的对象需要时间来实例化。为
例如,一个包含 50 到 100 个 Hibernate 映射文件的项目可能需要 10 到 20 秒才能完成
加载映射文件,并在运行每个测试中的每个测试之前产生该成本
夹具会导致整体测试运行速度变慢,从而降低开发人员的工作效率。ApplicationContext
WebApplicationContext
测试类通常为 XML 或 Groovy
声明资源位置数组
配置元数据(通常位于类路径中)或组件类数组
用于配置应用程序。这些位置或类与 或
类似于生产文件中指定的配置文件或其他配置文件
部署。web.xml
默认情况下,一旦加载,配置的内容将重复用于每个测试。
因此,每个测试套件仅产生一次设置成本,随后的测试执行
要快得多。在此上下文中,术语“测试套件”表示所有测试都在同一
JVM — 例如,所有测试都从给定项目的 Ant、Maven 或 Gradle 构建中运行
或模块。在极少数情况下,测试会损坏应用程序上下文并需要
重新加载(例如,通过修改 Bean 定义或应用程序的状态
object) 可以将 TestContext 框架配置为重新加载配置,并且
在执行下一个测试之前重新生成应用程序上下文。ApplicationContext
3.2.2. 测试夹具的依赖注入
当 TestContext
框架加载应用程序上下文时,它可以选择
使用依赖关系注入配置测试类的实例。这提供了一个
使用预配置的 Bean 设置测试夹具的便捷机制
应用程序上下文。这里的一个很大好处是可以重用应用程序上下文
跨各种测试场景(例如,用于配置 Spring 管理的对象
图、事务代理、实例等),从而避免了
需要为单个测试用例复制复杂的测试夹具设置。DataSource
例如,考虑一个场景,我们有一个类
()
实现域实体的数据访问逻辑。我们想写
测试以下方面的集成测试:HibernateTitleRepository
Title
-
Spring 配置:基本上,与 Bean 配置相关的一切都是否正确且存在?
HibernateTitleRepository
-
Hibernate 映射文件配置:是否映射正确,是否 正确的延迟加载设置到位?
-
的逻辑是: 是否配置了此类的实例 表现是否符合预期?
HibernateTitleRepository
请参阅使用 TestContext 框架注入测试夹具的依赖项。
3.2.3. 事务管理
在访问真实数据库的测试中,一个常见问题是它们对 持久性存储。即使使用开发数据库,对状态的更改也可能 影响将来的测试。此外,许多操作 — 例如插入或修改持久性 数据 — 不能在事务之外执行(或验证)。
TestContext
框架解决了这个问题。默认情况下,框架会创建和
为每个测试回滚事务。您可以编写可以假定存在的代码
的交易。如果在测试中调用事务代理对象,则它们的行为
根据其配置的事务语义正确。此外,如果测试
方法在事务中运行时删除所选表的内容
管理测试时,事务默认回滚,数据库返回到
它在执行测试之前的状态。事务支持由以下机构为测试提供
使用在测试的应用程序上下文中定义的 bean。PlatformTransactionManager
如果您希望提交事务(不寻常,但当您需要
特定的测试来填充或修改数据库),你可以告诉 TestContext
框架,通过使用 @Commit
注释使事务提交而不是回滚。
请参阅使用 TestContext 框架进行事务管理。
3.2.4. 集成测试的支持类
Spring TestContext
框架提供了几个支持类,这些类
简化集成测试的编写。这些基本测试类提供明确定义的
挂钩到测试框架以及方便的实例变量和方法,
它允许您访问:abstract
-
用于执行显式 Bean 查找或测试 整个上下文。
ApplicationContext
-
A ,用于执行SQL语句查询数据库。您可以使用这样的 在执行与数据库相关的操作之前和之后确认数据库状态的查询 应用程序代码,Spring 确保此类查询在相同的范围内运行 事务作为应用程序代码。当与 ORM 工具结合使用时,请确保 以避免误报。
JdbcTemplate
此外,您可能希望创建自己的自定义应用程序范围的超类 特定于项目的实例变量和方法。
请参阅 TestContext 框架的支持类。
3.3. JDBC测试支持
该软件包包含 ,这是一个
与JDBC相关的实用程序函数的集合,旨在简化标准数据库
测试方案。具体而言,提供以下静态实用程序
方法。org.springframework.test.jdbc
JdbcTestUtils
JdbcTestUtils
-
countRowsInTable(..)
:计算给定表中的行数。 -
countRowsInTableWhere(..)
:使用 provided 条款。WHERE
-
deleteFromTables(..)
:删除指定表中的所有行。 -
deleteFromTableWhere(..)
:使用提供的子句从给定表中删除行。WHERE
-
dropTables(..)
:删除指定的表。
该模块支持配置和启动嵌入式
数据库,可用于与数据库交互的集成测试。
有关详细信息,请参阅嵌入式数据库
支持和测试数据访问
具有嵌入式数据库的逻辑。 |
3.4. 注解
本节介绍在测试 Spring 应用程序时可以使用的注释。 它包括以下主题:
3.4.1. Spring 测试注解
Spring Framework 提供了以下一组特定于 Spring 的注解,您可以 可以在单元测试和集成测试中与 TestContext 框架结合使用。 有关详细信息,请参阅相应的 javadoc,包括 default 属性 值、属性别名和其他详细信息。
Spring 的测试注解包括以下内容:
@BootstrapWith
@BootstrapWith
是一个类级注解,可用于配置 Spring
TestContext Framework 是引导的。具体来说,您可以使用
指定自定义 .有关更多详细信息,请参阅引导 TestContext 框架的部分。@BootstrapWith
TestContextBootstrapper
@ContextConfiguration
@ContextConfiguration
定义用于确定如何
加载和配置集成测试。具体来说,声明应用程序上下文资源或
组件。ApplicationContext
@ContextConfiguration
locations
classes
资源位置通常是 XML 配置文件或
Groovy 脚本,位于
类路径,而组件类通常是类。然而
资源位置还可以引用文件系统和组件中的文件和脚本
类可以是类、类等。有关详细信息,请参阅组件类。@Configuration
@Component
@Service
下面的示例演示引用 XML 的批注
文件:@ContextConfiguration
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
// class body...
}
1 | 引用 XML 文件。 |
下面的示例演示引用类的批注:@ContextConfiguration
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
// class body...
}
1 | 引用类。 |
作为声明资源位置或组件类的替代或补充,
可用于声明类。
以下示例演示了这种情况:@ContextConfiguration
ApplicationContextInitializer
@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 提供对继承资源位置的支持,或者
配置类以及由超类声明的上下文初始值设定项
或封闭类。 |
有关更多详细信息,请参阅上下文管理、@Nested
测试类配置和
javadocs。@ContextConfiguration
@WebAppConfiguration
@WebAppConfiguration
是一个类级注释,可用于声明集成测试的
load 应为 .
仅存在 on 测试类即可确保为测试加载 a,使用默认值 作为 Web 应用程序根目录的路径(即
资源库路径)。资源库路径在后台用于创建一个 ,它用作测试的
.ApplicationContext
WebApplicationContext
@WebAppConfiguration
WebApplicationContext
"file:src/main/webapp"
MockServletContext
ServletContext
WebApplicationContext
以下示例演示如何使用注释:@WebAppConfiguration
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
要覆盖默认值,您可以使用
隐式属性。和资源前缀都是
支持。如果未提供资源前缀,则假定路径为文件系统
资源。下面的示例演示如何指定类路径资源:value
classpath:
file:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
// class body...
}
1 | 指定类路径资源。 |
请注意,必须在单个测试类或测试类中与
结合使用
等级制度。有关更多详细信息,请参阅 javadoc
@WebAppConfiguration
。@WebAppConfiguration
@ContextConfiguration
@ContextHierarchy
@ContextHierarchy
是类级注释,用于定义集成测试的实例层次结构。
应该是
使用一个或多个实例的列表声明,每个实例
在上下文层次结构中定义一个级别。以下示例演示了在单个测试类中使用 ( 也可以使用
在测试类层次结构中):ApplicationContext
@ContextHierarchy
@ContextConfiguration
@ContextHierarchy
@ContextHierarchy
@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...
}
如果需要合并或覆盖给定上下文级别的配置
层次结构 在测试类层次结构中,必须通过提供
每个对应的属性值相同
级别。请参见上下文层次结构和 javadoc @ContextHierarchy
有关更多示例。name
@ContextConfiguration
@ActiveProfiles
@ActiveProfiles
是用于声明哪个 Bean 的类级注释
定义配置文件在加载 for 时应处于活动状态
集成测试。ApplicationContext
以下示例指示配置文件应处于活动状态: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
|
@ActiveProfiles 提供对继承活动 Bean 定义概要文件的支持
由超类声明,默认情况下由封闭类声明。您还可以解析活动
Bean 定义配置文件通过实现自定义 ActiveProfilesResolver
并使用 的属性进行注册,以编程方式进行概要分析。resolver @ActiveProfiles
|
请参阅使用环境配置文件进行上下文配置、@Nested
测试类配置和 @ActiveProfiles
javadoc
示例和更多详细信息。
@TestPropertySource
@TestPropertySource
是一个类级注释,可用于配置
要添加到 for an 中加载的属性文件和内联属性的位置
集成测试。PropertySources
Environment
ApplicationContext
下面的示例演示如何从类路径声明属性文件:
@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
|
有关示例和更多详细信息,请参阅使用测试属性源进行上下文配置。
@DynamicPropertySource
@DynamicPropertySource
是一个方法级注释,可用于注册要添加到
for 中的 集合的动态属性
为集成测试加载。动态属性很有用
当您事先不知道属性的值时,例如,如果属性
由外部资源管理,例如由 Testcontainers
项目管理的容器。PropertySources
Environment
ApplicationContext
下面的示例演示如何注册动态属性:
@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 | 接受 a
作为参数。DynamicPropertyRegistry |
3 | 注册要从服务器延迟检索的动态属性。server.port
|
有关详细信息,请参阅使用动态属性源进行上下文配置。
@DirtiesContext
@DirtiesContext
表示底层 Spring 已经
在执行测试期间被弄脏(即,测试在
以某种方式 — 例如,通过改变单例 Bean 的状态),并且应该是
闭。当应用程序上下文被标记为脏时,它将从测试中删除
框架的缓存并关闭。因此,底层 Spring 容器是
为需要具有相同配置的上下文的任何后续测试重新生成
元数据。ApplicationContext
您可以同时用作类级和方法级注释
相同的类或类层次结构。在这种情况下,标记
在任何此类注释方法之前或之后以及当前之前或之后都是肮脏的
test 类,具体取决于配置的 和
。@DirtiesContext
ApplicationContext
methodMode
classMode
以下示例说明了何时会为各种上下文弄脏 配置方案:
-
在当前测试类之前,当在类模式设置为 的类上声明时。
BEFORE_CLASS
爪哇岛Kotlin@DirtiesContext(classMode = BEFORE_CLASS) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在当前测试类之前弄脏上下文。 -
在当前测试类之后,当在类模式设置为(即默认类模式)的类上声明时。
AFTER_CLASS
爪哇岛Kotlin@DirtiesContext (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在当前测试类之后弄脏上下文。 -
在当前测试类中的每个测试方法之前,当在类上声明时 mode 设置为
BEFORE_EACH_TEST_METHOD.
爪哇岛Kotlin@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1) class FreshContextTests { // some tests that require a new Spring container }
1 在每个测试方法之前弄脏上下文。 -
在当前测试类中的每个测试方法之后,当在类上声明时 mode 设置为
AFTER_EACH_TEST_METHOD.
爪哇岛Kotlin@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 在每个测试方法之后弄脏上下文。 -
在当前测试之前,在方法模式设置为 的方法上声明时。
BEFORE_METHOD
爪哇岛Kotlin@DirtiesContext(methodMode = BEFORE_METHOD) (1) @Test void testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
1 在当前测试方法之前弄脏上下文。 -
在当前测试之后,在方法模式设置为(即默认方法模式)的方法上声明时。
AFTER_METHOD
爪哇岛Kotlin@DirtiesContext (1) @Test void testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
1 在当前测试方法之后弄脏上下文。
如果在测试中使用,其上下文配置为上下文的一部分
层次结构,可以使用标志来控制如何
上下文缓存被清除。默认情况下,使用穷举算法来清除
上下文缓存,不仅包括当前级别,还包括所有其他上下文
共享当前测试通用的祖先上下文的层次结构。驻留在共同祖先的子层次结构中的所有实例
context 将从上下文缓存中删除并关闭。如果穷举算法是
对于特定用例,您可以指定更简单的电流电平算法,
如以下示例所示。@DirtiesContext
@ContextHierarchy
hierarchyMode
ApplicationContext
@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 | 使用当前级别的算法。 |
有关 和 算法的更多详细信息,请参阅
DirtiesContext.HierarchyMode
javadoc。EXHAUSTIVE
CURRENT_LEVEL
@TestExecutionListeners
@TestExecutionListeners
用于为特定测试类注册侦听器,其
子类及其嵌套类。如果您希望在全球范围内注册一个侦听器,则
应通过 TestExecutionListener
配置中描述的自动发现机制注册它。
下面的示例演示如何注册两个实现:TestExecutionListener
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 注册两个实现。TestExecutionListener
|
默认情况下,支持从
超类或封闭类。有关示例和更多详细信息,请参阅@Nested
测试类配置和
javadoc
@TestExecutionListeners
。如果您发现需要切换
返回到使用默认实现,请参阅注释
在注册 TestExecutionListener
实现中。@TestExecutionListeners
TestExecutionListener
@RecordApplicationEvents
@RecordApplicationEvents
是一个类级注释,用于指示 Spring TestContext Framework
记录在单个测试执行期间发布的所有应用程序事件。ApplicationContext
记录的事件可以通过测试中的
API 访问。ApplicationEvents
有关示例和更多详细信息,请参阅应用程序事件和 javadoc
@RecordApplicationEvents
。
@Commit
@Commit
指示事务测试方法的事务应为
在测试方法完成后提交。您可以直接使用
替换以更明确地传达代码的意图。
类似于 ,也可以声明为类级或方法级
注解。@Commit
@Rollback(false)
@Rollback
@Commit
以下示例演示如何使用注释:@Commit
@Commit (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 将测试结果提交到数据库。 |
@Rollback
@Rollback
指示事务测试方法的事务是否应
测试方法完成后回滚。如果 ,则滚动事务
返回。否则,将提交事务(另请参见 @Commit
)。Spring
中集成测试的回滚
TestContext Framework 默认为 even
即使未显式声明。true
true
@Rollback
声明为类级批注时,定义默认回滚
测试类层次结构中所有测试方法的语义。当声明为
方法级注解,定义特定测试的回滚语义
方法,可能会覆盖类级别或语义。@Rollback
@Rollback
@Rollback
@Commit
以下示例导致测试方法的结果不回滚(即 结果提交到数据库):
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 不要回滚结果。 |
@BeforeTransaction
@BeforeTransaction
指示带注释的方法应在
事务已启动,对于已配置为在
事务。 方法
不需要,并且可以在基于 Java 8 的接口上声明
方法。void
@Transactional
@BeforeTransaction
public
以下示例演示如何使用注释:@BeforeTransaction
@BeforeTransaction (1)
void beforeTransaction() {
// logic to be run before a transaction is started
}
1 | 在事务之前运行此方法。 |
@AfterTransaction
@AfterTransaction
指示带注释的方法应在
事务已结束,对于已配置为在
事务。 方法
不需要,并且可以在基于 Java 8 的接口上声明
方法。void
@Transactional
@AfterTransaction
public
@AfterTransaction (1)
void 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 | 为此测试运行两个脚本。 |
有关详细信息,请参阅使用 @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 脚本中设置注释前缀和分隔符。 |
@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
|
下面的示例演示如何在方法级别使用。@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
|
@SqlGroup
@SqlGroup
是聚合多个批注的容器批注。您可以
本机使用来声明多个嵌套注解,也可以使用它
结合 Java 8 对可重复注解的支持,可以
在同一类或方法上多次声明,隐式生成此容器
注解。以下示例演示如何声明 SQL 组:@Sql
@SqlGroup
@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 脚本。 |
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 框架中,您可以使用和
在 中配置的任何应用程序组件上的标准语义。
但是,这些生命周期注释在实际测试类中的使用受到限制。 如果测试类中的方法带有
的注释,则该方法将运行
在底层测试框架的任何 before 方法(例如,方法
用 JUnit Jupiter 的 注解 ),这适用于
测试类。另一方面,如果测试类中的方法带有 的注释,则该方法永远不会运行。因此,在测试类中,我们建议
您可以使用来自底层测试框架的测试生命周期回调,而不是 和 。 |
3.4.3. Spring JUnit 4 测试注解
@IfProfileValue
@IfProfileValue
指示已为特定测试启用带注释的测试
环境。如果 configured 返回
,则启用测试。否则,测试将被禁用,并且实际上,
忽视。ProfileValueSource
value
name
您可以在类级别和/或方法级别进行应用。
对于任何 的类级用法,其优先级优先于方法级用法
该类或其子类中的方法。具体而言,如果符合以下条件,则启用测试
在类级别和方法级别都启用。缺少 意味着隐式启用测试。这类似于 JUnit 4 注解的语义,只不过
always
的存在会禁用测试。@IfProfileValue
@IfProfileValue
@IfProfileValue
@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”时,才运行此测试。 |
或者,您可以使用(带有语义)列表进行配置,以在
JUnit 4 环境中实现对测试组的类似 TestNG 的支持。
请看以下示例:@IfProfileValue
values
OR
@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 | 为单元测试和集成测试运行此测试。 |
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration
是指定类型的类级批注
在检索通过注释配置的配置文件值时使用。如果未为
test,默认使用。下面的示例演示如何
用:ProfileValueSource
@IfProfileValue
@ProfileValueSourceConfiguration
SystemProfileValueSource
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public 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 | 将测试的时间段设置为 1 秒。 |
Spring 的注解与 JUnit 4
的支持具有不同的语义。具体来说,由于 JUnit 4 处理测试执行超时的方式
(即,通过在单独的 中执行测试方法 ),如果测试时间过长,则抢先使测试失败。另一方面,春天的
hand,不会先发制人地使测试失败,而是等待测试完成
在失败之前。@Timed
@Test(timeout=…)
Thread
@Test(timeout=…)
@Timed
@Repeat
@Repeat
指示必须重复运行带批注的测试方法。数量
测试方法的运行时间在注释中指定。
要重复的执行范围包括测试方法本身的执行,如
以及测试夹具的任何设置或拆卸。与 SpringMethodRule
一起使用时,该范围还包括
通过实现准备测试实例。这
以下示例演示如何使用注释:TestExecutionListener
@Repeat
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
// ...
}
1 | 重复此测试十次。 |
3.4.4. Spring JUnit Jupiter 测试注解
与 SpringExtension
和 JUnit Jupiter
结合使用时,支持以下注解
(即 JUnit 5 中的编程模型):
@SpringJUnitConfig
@SpringJUnitConfig
是一个组合注解,它结合了 JUnit Jupiter
和
Spring TestContext 框架。它可以在班级级别用作临时
替换为 。关于配置选项,唯一的
和 之间的区别是那个组件
可以使用 中的属性声明类。@ExtendWith(SpringExtension.class)
@ContextConfiguration
@ContextConfiguration
@ContextConfiguration
@SpringJUnitConfig
value
@SpringJUnitConfig
以下示例演示如何使用注释指定
配置类:@SpringJUnitConfig
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置类。 |
以下示例演示如何使用注释来指定
配置文件的位置:@SpringJUnitConfig
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置文件的位置。 |
有关@SpringJUnitConfig
和更多详细信息,请参阅上下文管理以及 javadoc。@ContextConfiguration
@SpringJUnitWebConfig
@SpringJUnitWebConfig
是一个组合注解,它结合了 JUnit
Jupiter 和 Spring TestContext 框架。您可以在课堂上使用它
级别作为 和 的直接替代品。
关于配置选项,和之间的唯一区别是可以使用 中的属性来声明组件类。此外,只能通过使用 中的属性来覆盖
中的属性。@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
@ContextConfiguration
@WebAppConfiguration
@ContextConfiguration
@SpringJUnitWebConfig
value
@SpringJUnitWebConfig
value
@WebAppConfiguration
resourcePath
@SpringJUnitWebConfig
以下示例演示如何使用注释指定
配置类:@SpringJUnitWebConfig
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置类。 |
以下示例演示如何使用注释来指定
配置文件的位置:@SpringJUnitWebConfig
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置文件的位置。 |
有关更多详细信息,请参阅上下文管理以及 javadoc for
@SpringJUnitWebConfig
、@ContextConfiguration
和 @WebAppConfiguration
。
@TestConstructor
@TestConstructor
是一个类型级注释,用于配置参数
的测试类构造函数是从测试的 .ApplicationContext
如果测试类中不存在或元存在,则默认测试
将使用构造函数 AutoWire 模式。有关如何更改的详细信息,请参阅下面的提示
默认模式。但是请注意,本地声明 on a
构造函数优先于两者和默认模式。@TestConstructor
@Autowired
@TestConstructor
更改默认测试构造函数自动连线模式
可以通过将
JVM 系统属性设置为 来更改缺省测试构造函数自动连线模式。或者,
默认模式可以通过 从 Spring Framework 5.3 开始,默认模式也可以配置为 JUnit Platform 配置参数。 如果未设置该属性,则测试类
构造函数不会自动连接。 |
从
Spring Framework 5.2 开始,仅支持结合使用
与 JUnit Jupiter 一起使用。请注意,是
通常会自动为您注册 - 例如,当使用注释(例如 和/或各种与测试相关的注释)时
Spring Boot
测试。@TestConstructor SpringExtension SpringExtension @SpringJUnitConfig @SpringJUnitWebConfig
|
@NestedTestConfiguration
@NestedTestConfiguration
是一个类型级批注,用于配置如何
Spring 测试配置注释在封闭的类层次结构中进行处理
用于内部测试类。
如果在测试类上不存在或元存在,则在其
超类型层次结构,或在其封闭类层次结构中,默认封闭
将使用配置继承模式。有关如何操作的详细信息,请参阅下面的提示
更改默认模式。@NestedTestConfiguration
更改默认封闭配置继承模式
默认的封闭配置继承模式是 ,但它可以是
通过将 JVM 系统属性设置为 进行更改。或者,可以通过 |
Spring TestContext 框架支持
以下注释。@NestedTestConfiguration
通常只有结合使用才有意义
在 JUnit Jupiter 中使用测试类;但是,可能还有其他检查
支持 Spring 的框架和利用它的嵌套测试类
注解。@NestedTestConfiguration @Nested |
有关示例和进一步内容,请参阅@Nested
测试类配置
详。
@EnabledIf
@EnabledIf
用于表示带注释的
JUnit Jupiter 测试类或测试方法
已启用,如果提供的计算结果为 。
具体而言,如果表达式的计算结果为或等于(忽略大小写),则启用测试。当应用于类级别时,所有测试方法
默认情况下,该类也会自动启用。expression
true
Boolean.TRUE
String
true
表达式可以是以下任何一种:
-
Spring 表达式语言 (SpEL) 表达式。例如:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
Spring
环境中
可用属性的占位符。 例如:@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 {}
|
从 JUnit
5.7 开始,JUnit Jupiter 也有一个名为 .因此
如果您希望使用 Spring 的支持,请确保导入注解类型
从正确的包装。 |
@DisabledIf
@DisabledIf
用于表示带注释的
JUnit Jupiter 测试类或测试
方法被禁用,如果提供的计算结果为 。具体而言,如果表达式的计算结果等于或等于
to(忽略大小写),则禁用测试。在类级别应用时,所有
该类中的测试方法也会自动禁用。expression
true
Boolean.TRUE
String
true
表达式可以是以下任何一种:
-
Spring 表达式语言 (SpEL) 表达式。例如:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
Spring
环境中
可用属性的占位符。 例如:@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 {}
|
从 JUnit
5.7 开始,JUnit Jupiter 也有一个名为 .因此
如果您希望使用 Spring 的支持,请确保导入注解类型
从正确的包装。 |
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 { }
如果我们发现我们在基于 JUnit 4 的 测试套件中,我们可以通过引入自定义组合注释来减少重复 集中了 Spring 的通用测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后,我们可以使用自定义注解来简化
配置各个基于 JUnit 4 的测试类,如下所示:@TransactionalDevTestConfig
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public 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 { }
如果我们发现我们在 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 { }
然后,我们可以使用自定义注解来简化
基于 JUnit Jupiter
的各个测试类的配置,如下所示:@TransactionalDevTestConfig
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
由于 JUnit Jupiter 支持使用 、
、 、
和其他作为元注释,您还可以在
测试方法级别。例如,如果我们希望创建一个组合注释,该注释结合
来自 JUnit Jupiter 的注解和来自 Spring 的注解,我们可以创建一个注解,因为
遵循:@Test
@RepeatedTest
ParameterizedTest
@Test
@Tag
@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 { }
然后,我们可以使用自定义注解来简化
基于 JUnit Jupiter
的各个测试方法的配置,如下所示:@TransactionalIntegrationTest
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
有关详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
3.5. Spring TestContext 框架
Spring TestContext
Framework(位于包中)提供通用的、注释驱动的单元和集成测试支持,即
与正在使用的测试框架无关。TestContext 框架还放置了一个很好的
约定比配置更重要,您的合理默认值
可以通过基于注释的配置进行覆盖。org.springframework.test.context
除了通用测试基础结构之外,TestContext
框架还提供
显式支持 JUnit 4、JUnit Jupiter(又名 JUnit 5)和 TestNG。对于 JUnit 4 和
TestNG,Spring 提供了支持类。此外,Spring 还提供了一个自定义
JUnit 和自定义 JUnit 4 的 JUnit 以及 JUnit 的自定义
Jupiter 让你编写所谓的 POJO 测试类。POJO 测试类不是
需要扩展特定类层次结构,例如支持类。abstract
Runner
Rules
Extension
abstract
以下部分概述了 TestContext 框架的内部结构。 如果您只对使用框架感兴趣,而对扩展它不感兴趣 使用您自己的自定义侦听器或自定义加载器,请随时直接转到 配置(上下文管理、依赖注入、事务 management)、支持类和注释支持部分。
3.5.1. 关键抽象
框架的核心由类、、和接口组成。为每个测试类创建一个(例如,用于执行
JUnit Jupiter 中单个测试类中的所有测试方法)。这
反过来,管理保存当前测试上下文的 a。随着测试的进行,还会更新
并委托给实现,这些实现检测实际
通过提供依赖项注入、管理事务等来测试执行。A 负责为给定测试加载 a
类。请参阅 javadoc 和
Spring 测试套件,以获取更多信息和各种实现的示例。TestContextManager
TestContext
TestExecutionListener
SmartContextLoader
TestContextManager
TestContextManager
TestContext
TestContextManager
TestContext
TestExecutionListener
SmartContextLoader
ApplicationContext
TestContext
TestContext
封装运行测试的上下文(与
实际测试框架),并提供上下文管理和缓存支持
它负责的测试实例。如果需要,还会委托给 a 以加载 。TestContext
SmartContextLoader
ApplicationContext
TestContextManager
TestContextManager
is the main entry point into the
Spring TestContext Framework and is
responsible for managing a single and signaling events to each registered
at well-defined test execution points:TestContext
TestExecutionListener
-
Prior to any “before class” or “before all” methods of a particular testing framework.
-
Test instance post-processing.
-
Prior to any “before” or “before each” methods of a particular testing framework.
-
Immediately before execution of the test method but after test setup.
-
Immediately after execution of the test method but before test tear down.
-
After any “after” or “after each” methods of a particular testing framework.
-
After any “after class” or “after all” methods of a particular testing framework.
TestExecutionListener
TestExecutionListener
defines the API for reacting to
test-execution events published by
the with which the listener is registered. See TestExecutionListener
Configuration.TestContextManager
Context Loaders
ContextLoader
is a strategy interface for loading an for an
integration test managed by the Spring TestContext Framework. You should implement
instead of this interface to provide support for component classes,
active bean definition profiles, test property sources, context hierarchies, and
support.ApplicationContext
SmartContextLoader
WebApplicationContext
SmartContextLoader
is an extension of the interface
that supersedes the
original minimal SPI. Specifically, a can choose to
process resource locations, component classes, or context initializers. Furthermore, a
can set active bean definition profiles and test property sources in
the context that it
loads.ContextLoader
ContextLoader
SmartContextLoader
SmartContextLoader
Spring provides the following implementations:
-
DelegatingSmartContextLoader
: One of two default loaders, it delegates internally to an , a , or a , depending either on the configuration declared for the test class or on the presence of default locations or default configuration classes. Groovy support is enabled only if Groovy is on the classpath.AnnotationConfigContextLoader
GenericXmlContextLoader
GenericGroovyXmlContextLoader
-
WebDelegatingSmartContextLoader
: One of two default loaders, it delegates internally to an , a , or a , depending either on the configuration declared for the test class or on the presence of default locations or default configuration classes. A web is used only if is present on the test class. Groovy support is enabled only if Groovy is on the classpath.AnnotationConfigWebContextLoader
GenericXmlWebContextLoader
GenericGroovyXmlWebContextLoader
ContextLoader
@WebAppConfiguration
-
AnnotationConfigContextLoader
: Loads a standard from component classes.ApplicationContext
-
AnnotationConfigWebContextLoader
: Loads a from component classes.WebApplicationContext
-
GenericGroovyXmlContextLoader
: Loads a standard from resource locations that are either Groovy scripts or XML configuration files.ApplicationContext
-
GenericGroovyXmlWebContextLoader
: Loads a from resource locations that are either Groovy scripts or XML configuration files.WebApplicationContext
-
GenericXmlContextLoader
: Loads a standard from XML resource locations.ApplicationContext
-
GenericXmlWebContextLoader
: Loads a from XML resource locations.WebApplicationContext
3.5.2. Bootstrapping the TestContext Framework
The default configuration for
the internals of the Spring TestContext Framework is
sufficient for all common use cases. However, there are times when a development team or
third party framework would like to change the default , implement a
custom or , augment the default sets of
and implementations, and so on. For
such low-level control over how the TestContext framework operates, Spring provides a
bootstrapping strategy.ContextLoader
TestContext
ContextCache
ContextCustomizerFactory
TestExecutionListener
TestContextBootstrapper
defines the SPI for bootstrapping
the TestContext framework. A
is used by the to load the
implementations for the current test and to build the
that it manages. You can configure a custom bootstrapping strategy for a
test class (or test class hierarchy) by using , either directly or as a
meta-annotation. If a bootstrapper is not explicitly configured by using
, either the or the
is used, depending on the presence of .TestContextBootstrapper
TestContextManager
TestExecutionListener
TestContext
@BootstrapWith
@BootstrapWith
DefaultTestContextBootstrapper
WebTestContextBootstrapper
@WebAppConfiguration
Since the SPI is likely to
change in the future (to accommodate
new requirements), we strongly encourage implementers not to implement this interface
directly but rather to extend or one of its concrete
subclasses instead.TestContextBootstrapper
AbstractTestContextBootstrapper
3.5.3. 配置TestExecutionListener
Spring 提供了以下已注册的实现
默认情况下,完全按以下顺序排列:TestExecutionListener
-
ServletTestExecutionListener
:将 Servlet API 模拟配置为 .WebApplicationContext
-
DirtiesContextBeforeModesTestExecutionListener
:处理“之前”模式的注释。@DirtiesContext
-
ApplicationEventsTestExecutionListener
:提供对ApplicationEvents
的支持。 -
DependencyInjectionTestExecutionListener
:为测试提供依赖注入 实例。 -
DirtiesContextTestExecutionListener
:处理 的注释 “之后”模式。@DirtiesContext
-
TransactionalTestExecutionListener
:提供事务性测试执行 默认回滚语义。 -
SqlScriptsTestExecutionListener
:运行使用注释配置的 SQL 脚本。@Sql
-
EventPublishingTestExecutionListener
:将测试执行事件发布到测试(请参阅测试执行事件)。ApplicationContext
注册实现TestExecutionListener
您可以显式地为测试类注册实现,其
子类,以及使用注释的嵌套类。有关详细信息和示例,请参阅注释支持和 javadoc 以获取@TestExecutionListeners
。TestExecutionListener
@TestExecutionListeners
切换到默认实现
TestExecutionListener
如果您扩展了带注释的类,并且您需要
切换到使用默认的侦听器集,你可以用
以后。 爪哇岛
Kotlin
|
自动发现默认实现TestExecutionListener
使用 is 注册实现
适用于在有限的测试场景中使用的自定义侦听器。但是,它可以
如果需要在整个测试套件中使用自定义侦听器,则会变得很麻烦。这
此问题已通过支持通过该机制自动发现默认实现得到解决。TestExecutionListener
@TestExecutionListeners
TestExecutionListener
SpringFactoriesLoader
具体来说,该模块在
其属性文件。第三方框架和开发人员
可以将自己的实现贡献到默认列表中
侦听器以同样的方式通过他们自己的属性
文件。spring-test
TestExecutionListener
org.springframework.test.context.TestExecutionListener
META-INF/spring.factories
TestExecutionListener
META-INF/spring.factories
订购实现TestExecutionListener
当 TestContext 框架发现默认实现时
通过上述机制,实例化的监听器使用
Spring's ,它尊重 Spring 的接口和用于排序的注解。 Spring 提供的所有默认实现都使用
适当的值。因此,第三方框架和开发人员应确保
它们的默认实现按正确的顺序注册
通过实现或声明 .有关核心默认实现的方法,请参阅 javadoc,了解以下内容的详细信息
值分配给每个核心侦听器。TestExecutionListener
SpringFactoriesLoader
AnnotationAwareOrderComparator
Ordered
@Order
AbstractTestExecutionListener
TestExecutionListener
Ordered
TestExecutionListener
Ordered
@Order
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...
}
这种方法的挑战在于,它要求开发人员确切地知道
默认情况下注册了哪些侦听器。此外,默认侦听器集可以
从一个版本到另一个版本的更改 — 例如,WAS
在 Spring Framework 4.1 中引入,并在 Spring Framework 4.2 中引入。此外,像 Spring 这样的第三方框架
Boot 和 Spring Security 使用上述自动发现机制注册自己的默认实现。SqlScriptsTestExecutionListener
DirtiesContextBeforeModesTestExecutionListener
TestExecutionListener
为了避免必须知道并重新声明所有缺省侦听器,可以将
的属性设置为 。 指示本地声明的侦听器应与
默认侦听器。合并算法可确保从
列表,并且根据语义对合并的侦听器集进行排序
的,如排序 TestExecutionListener
实现中所述。
如果侦听器实现或注释了 ,它可以影响
它与默认值合并的位置。否则,本地声明的侦听器
在合并时附加到默认侦听器列表中。mergeMode
@TestExecutionListeners
MergeMode.MERGE_WITH_DEFAULTS
MERGE_WITH_DEFAULTS
AnnotationAwareOrderComparator
Ordered
@Order
例如,如果上一个示例中的类
将其值(例如,)配置为小于 的顺序(恰好是 ),然后可以自动与
默认值,前面的示例可以
替换为以下内容:MyCustomTestExecutionListener
order
500
ServletTestExecutionListener
1000
MyCustomTestExecutionListener
ServletTestExecutionListener
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
3.5.4. Application Events
Since Spring Framework 5.3.3,
the TestContext framework provides support for recording
application events published in the
so that assertions can be performed against those events within
tests. All events published during the execution of a single test are made available via
the API which allows you to process the events as a
.ApplicationContext
ApplicationEvents
java.util.Stream
To use in your tests, do the
following.ApplicationEvents
-
Ensure that your test class is annotated or meta-annotated with
@RecordApplicationEvents
. -
确保已注册。但请注意, 默认注册,只需要 如果您具有自定义配置,则手动注册,其中不包括默认侦听器。
ApplicationEventsTestExecutionListener
ApplicationEventsTestExecutionListener
@TestExecutionListeners
-
在测试和生命周期方法(例如 JUnit Jupiter 中的 和 方法)中使用该类型的字段和实例。
ApplicationEvents
@Autowired
ApplicationEvents
@BeforeEach
@AfterEach
-
使用 SpringExtension for JUnit Jupiter 时,您可以声明一个方法 测试或生命周期方法中的类型参数作为替代项 添加到测试类中的字段。
ApplicationEvents
@Autowired
-
以下测试类使用 for
JUnit、Jupiter 和 AssertJ 来断言应用程序事件的类型
在 Spring 管理的组件中调用方法时发布:SpringExtension
@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 | 使用 API
计算已发布的事件数。ApplicationEvents OrderSubmitted
|
有关 API 的更多详细信息,请参阅
ApplicationEvents
javadoc。ApplicationEvents
3.5.5. 测试执行事件
Spring Framework 5.2 中引入的
实现自定义 .中的组件
test 可以监听 发布的以下事件,每个事件对应 API 中的一个方法。EventPublishingTestExecutionListener
TestExecutionListener
ApplicationContext
EventPublishingTestExecutionListener
TestExecutionListener
-
BeforeTestClassEvent
-
PrepareTestInstanceEvent
-
BeforeTestMethodEvent
-
BeforeTestExecutionEvent
-
AfterTestExecutionEvent
-
AfterTestMethodEvent
-
AfterTestClassEvent
这些事件可能出于各种原因而被使用,例如重置模拟
Bean 或跟踪
测试执行。使用测试执行事件而不是实现测试执行事件的一个优点
自定义是测试执行事件可以由任何
春豆在测试中注册,这样的豆子可能会受益
直接从依赖注入和 的其他功能中获取。在
相比之下,a 不是
.TestExecutionListener
ApplicationContext
ApplicationContext
TestExecutionListener
ApplicationContext
默认注册;然而,它只是
如果已加载,则发布事件。这样可以防止不必要或过早加载。 因此,在另一个
加载之前,不会发布 。例如,使用
注册的默认实现集 A 不会为使用
特定的测试,但将发布
同一测试套件中使用相同测试的任何后续测试类,因为在后续测试时已经加载了上下文
类运行(只要上下文尚未从 VIA 或 max-size 逐出策略中删除)。 如果您希望确保始终为每个测试发布
类,您需要在回调中注册一个加载 的,并且必须在 . 同样,if 用于从
上下文缓存 在给定测试类中的最后一个测试方法之后,将不会为该测试类发布。 |
为了侦听测试执行事件,Spring
Bean 可以选择实现接口。或者,侦听器
方法可以注释并配置为侦听
上面列出的特定事件类型(请参阅基于注释的事件侦听器)。
由于这种方法的流行,Spring 提供了以下专用注解来简化测试执行事件侦听器的注册。
这些注释驻留在包中。org.springframework.context.ApplicationListener
@EventListener
@EventListener
org.springframework.test.context.event.annotation
-
@BeforeTestClass
-
@PrepareTestInstance
-
@BeforeTestMethod
-
@BeforeTestExecution
-
@AfterTestExecution
-
@AfterTestMethod
-
@AfterTestClass
异常处理
默认情况下,如果测试执行事件侦听器在使用
事件,该异常将传播到正在使用的底层测试框架(例如
JUnit 或 TestNG)。例如,如果 a 的消耗导致
如果出现异常,相应的测试方法将因异常而失败。在
相反,如果异步测试执行事件侦听器抛出异常,则
异常不会传播到基础测试框架。有关更多详细信息
异步异常处理,请查阅类级 javadoc 了解。BeforeTestMethodEvent
@EventListener
异步侦听器
如果希望特定的测试执行事件侦听器异步处理事件,
您可以使用 Spring 的常规@Async
支持。有关更多详细信息,请查阅类级
javadoc 以获取 。@EventListener
3.5.6. 上下文管理
每个实例都为测试实例提供上下文管理和缓存支持
它对此负责。测试实例不会自动接收对
配置。但是,如果测试类实现该接口,则提供对
到测试实例。请注意,并实现,因此,
提供对 自动.TestContext
ApplicationContext
ApplicationContextAware
ApplicationContext
AbstractJUnit4SpringContextTests
AbstractTestNGSpringContextTests
ApplicationContextAware
ApplicationContext
@Autowired
ApplicationContext
作为实现接口的替代方法,您可以注入
测试类的应用程序上下文,通过
字段或 setter
方法,如以下示例所示:
爪哇岛
Kotlin
同样,如果测试配置为加载
,则可以注入
将 Web 应用程序上下文添加到测试中,如下所示:
爪哇岛
Kotlin
依赖关系注入由
提供,默认配置为
(请参阅测试夹具的依赖注入)。 |
使用 TestContext
框架的测试类不需要扩展任何特定的
类或实现特定接口以配置其应用程序上下文。相反
配置是通过在
班级级别。如果测试类未显式声明应用程序上下文资源
位置或组件类,则配置决定了如何加载
来自默认位置或默认配置类的上下文。除了上下文
资源位置和组件类,也可以配置应用程序上下文
通过应用程序上下文初始值设定项。@ContextConfiguration
ContextLoader
以下各节解释了如何使用 Spring
的注解来
使用 XML 配置文件、Groovy 脚本、
组件类(通常为类)或上下文初始值设定项。
或者,您可以实现和配置自己的自定义
高级用例。@ContextConfiguration
ApplicationContext
@Configuration
SmartContextLoader
使用 XML 资源进行上下文配置
若要使用 XML 配置文件加载测试,请注释
测试类,并使用
一个数组,其中包含 XML 配置元数据的资源位置。普通或
相对路径(例如,)被视为类路径资源,即
相对于定义测试类的包。以斜杠开头的路径
被视为绝对类路径位置(例如,)。一个
表示资源 URL(即以 、 、 等为前缀的路径)的路径按原样使用。ApplicationContext
@ContextConfiguration
locations
context.xml
/org/example/config.xml
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 文件列表。 |
@ContextConfiguration
支持通过
标准 Java 属性。因此,如果您不需要声明额外的
中的属性,可以省略属性名称的声明,并使用速记格式声明资源位置
以下示例演示了:locations
value
@ContextConfiguration
locations
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | 在不使用属性的情况下指定
XML 文件。location |
如果从注释中省略 和 属性,则
TestContext 框架会尝试检测默认值
XML 资源位置。具体来说,并根据测试的名称检测默认位置
类。如果您的类被命名为 ,则加载您的
应用程序上下文。以下
示例演示如何执行此操作:locations
value
@ContextConfiguration
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 | 从默认位置加载配置。 |
使用 Groovy 脚本进行上下文配置
要使用使用 Groovy Bean 定义 DSL 的 Groovy 脚本为测试加载
测试类,并使用包含 Groovy 脚本资源位置的数组配置 or 属性。资源
Groovy 脚本的查找语义与 XML 配置文件的查找语义相同。ApplicationContext
@ContextConfiguration
locations
value
启用 Groovy 脚本支持
支持使用 Groovy
脚本在 Spring 中加载
如果 Groovy 位于类路径上,则会自动启用 TestContext Framework。ApplicationContext
|
以下示例演示如何指定 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...
}
如果从注解中省略 和
属性,TestContext 框架会尝试检测默认的 Groovy 脚本。
具体而言,并根据测试类的名称检测默认位置。如果类已命名,则 Groovy 上下文加载器将从
加载应用程序上下文。以下示例演示如何使用
默认值:locations
value
@ContextConfiguration
GenericGroovyXmlContextLoader
GenericGroovyXmlWebContextLoader
com.example.MyTest
"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 | 从默认位置加载配置。 |
同时声明 XML 配置和
Groovy 脚本
您可以使用以下命令同时声明
XML 配置文件和 Groovy 脚本
的 or 属性。如果路径指向
配置的资源位置以 结尾,它是使用 .否则,将使用
. 下面的清单显示了如何在集成测试中将两者结合起来: 爪哇岛
Kotlin
|
使用组件类进行上下文配置
要使用组件类为测试加载(请参阅基于 Java 的容器配置),可以对测试进行注释
类并使用数组配置属性
包含对组件类的引用。以下示例演示如何执行此操作:ApplicationContext
@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 | 指定组件类。 |
组件类
术语“组件类”可以指以下任何一种:
有关更多信息,请参阅
|
如果从注释中省略该属性,则
TestContext 框架尝试检测默认配置类是否存在。
具体来说,并检测满足以下要求的测试类的所有嵌套类
配置类实现,如 @Configuration
javadoc 中指定。
请注意,配置类的名称是任意的。此外,测试类可以
如果需要,包含多个嵌套配置类。在以下内容中
示例中,该类声明了一个嵌套的配置类
named 自动用于加载测试的
类:classes
@ContextConfiguration
AnnotationConfigContextLoader
AnnotationConfigWebContextLoader
static
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
|
混合 XML、Groovy 脚本和组件类
有时可能需要混合 XML
配置文件、Groovy 脚本和
组件类(通常是类)来配置测试。例如,如果在
production,您可以决定要使用类来配置
用于测试的特定 Spring 管理组件,反之亦然。@Configuration
ApplicationContext
@Configuration
此外,一些第三方框架(如
Spring Boot)提供了一流的
支持从不同类型的资源加载
(例如,XML 配置文件、Groovy 脚本和类)。从历史上看,Spring Framework 不支持这一点
标准部署。因此,大多数实现
Spring Framework 在模块中仅支持一种资源类型
对于每个测试上下文。但是,这并不意味着您不能同时使用两者。一
一般规则的例外是 和 同时支持 XML 配置文件和 Groovy
脚本。此外,第三方框架可以选择支持
声明 both 和 through , 和
TestContext
框架中的标准测试支持,您有以下选项。ApplicationContext
@Configuration
SmartContextLoader
spring-test
GenericGroovyXmlContextLoader
GenericGroovyXmlWebContextLoader
locations
classes
@ContextConfiguration
如果要使用资源位置(例如,XML
或 Groovy)和类来配置测试,则必须选择一个作为入口点,并且该入口点必须
包含或导入另一个。例如,在 XML 或 Groovy 脚本中,您可以通过使用组件扫描或将它们定义为普通
Spring 来包含类
bean,而在类中,您可以使用导入 XML
配置文件或 Groovy 脚本。请注意,此行为在语义上是等效的
了解如何在生产环境中配置应用程序:在生产配置中,您
定义一组 XML 或 Groovy 资源位置,或者定义一组从中加载生产的类,但您仍然拥有
自由包含或导入其他类型的配置。@Configuration
@Configuration
@Configuration
@ImportResource
@Configuration
ApplicationContext
使用上下文初始值设定项进行上下文配置
若要使用上下文初始值设定项为测试配置,请执行以下操作:
使用一个数组对测试类进行注释,并使用一个数组配置该属性,该数组包含对实现
的类的引用。然后,声明的上下文初始值设定项用于
初始化为测试加载的。请注意,
每个声明的初始值设定项支持的具体类型
必须与正在使用的创建的类型兼容(通常为 )。此外,
调用初始值设定项的顺序取决于它们是实现 Spring 的接口,还是使用 Spring
的注解或标准注解进行注解。以下示例演示如何使用初始值设定项:ApplicationContext
@ContextConfiguration
initializers
ApplicationContextInitializer
ConfigurableApplicationContext
ConfigurableApplicationContext
ApplicationContext
SmartContextLoader
GenericApplicationContext
Ordered
@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 | 使用配置类和初始值设定项指定配置。 |
您还可以省略 XML
配置文件、Groovy 脚本或
组件类,而是只声明类,然后负责注册 bean
在上下文中 — 例如,通过以编程方式从 XML 加载 Bean 定义
文件或配置类。以下示例演示如何执行此操作:@ContextConfiguration
ApplicationContextInitializer
@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
支持布尔值和属性,这些属性表示资源位置或组件类和上下文
应继承由超类声明的初始值设定项。两者的默认值
flags 是 。这意味着测试类继承资源位置或
组件类以及由任何超类声明的上下文初始值设定项。
具体而言,将追加测试类的资源位置或组件类
添加到资源位置或超类声明的带注释的类的列表。
同样,给定测试类的初始值设定项将添加到初始值设定项集中
由测试超类定义。因此,子类可以选择扩展资源
位置、组件类或上下文初始值设定项。inheritLocations
inheritInitializers
true
如果 中的 or 属性设置为
,则资源位置或组件类和上下文
初始值设定项,分别用于测试类 Shadow 并有效地替换
由超类定义的配置。inheritLocations
inheritInitializers
@ContextConfiguration
false
从 Spring Framework 5.3
开始,测试配置也可以继承自 enclosed
类。有关详细信息@Nested 请参阅测试类配置。
|
在下一个使用 XML
资源位置的示例中,按该顺序从 和 加载 for。
因此,中定义的 Bean 可以覆盖(即替换)那些
中定义。下面的示例演示一个类如何扩展
另一个,同时使用自己的配置文件和超类的配置文件: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 | 子类中定义的配置文件。 |
同样,在下一个使用组件类的示例中,for
是从 和 类加载的,其中
次序。因此,中定义的 Bean 可以覆盖(即替换)
中定义的那些。下面的示例演示一个类如何扩展
另一个,同时使用自己的配置类和超类的配置类: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 | 子类中定义的配置类。 |
在下一个使用上下文初始值设定项的示例中,通过使用
和 来初始化 for。注意
但是,调用初始值设定项的顺序取决于它们是否
实现 Spring 的接口或使用 Spring 的注解进行注解
或标准注释。下面的示例演示一个类如何
扩展另一个并同时使用自己的初始值设定项和超类的初始值设定项:ApplicationContext
ExtendedTest
BaseInitializer
ExtendedInitializer
Ordered
@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 | 在子类中定义的初始值设定项。 |
使用环境配置文件进行上下文配置
Spring Framework
对环境和配置文件的概念提供了一流的支持
(又名“bean 定义配置文件”),并且可以将集成测试配置为激活
用于各种测试场景的特定 Bean 定义配置文件。这是通过以下方式实现的
使用注解对测试类进行注解,并提供
加载测试时应激活的配置文件。@ActiveProfiles
ApplicationContext
您可以与SPI的任何实现一起使用,但不支持旧SPI的实现。@ActiveProfiles SmartContextLoader @ActiveProfiles ContextLoader
|
考虑两个 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
}
}
运行时,它会从类路径根目录中的配置文件加载。如果检查
,您可以看到 Bean 依赖于 Bean。但是,未定义为顶级 Bean。相反,被定义了三次:在配置文件中,在配置文件中,
和在配置文件中。TransferServiceTest
ApplicationContext
app-config.xml
app-config.xml
accountRepository
dataSource
dataSource
dataSource
production
dev
default
通过注释 ,我们指示
Spring
TestContext Framework 加载活动配置文件设置为 .因此,将创建一个嵌入式数据库并填充测试数据,并且
Bean 与开发相关的参考。
这可能是我们在集成测试中想要的。TransferServiceTest
@ActiveProfiles("dev")
ApplicationContext
{"dev"}
accountRepository
DataSource
有时将 Bean
分配给配置文件很有用。缺省值内的 Bean
仅当没有专门激活其他配置文件时,才会包含配置文件。你可以使用
这是为了定义要在应用程序的缺省状态中使用的“回退”bean。为
例如,您可以显式提供 和 配置文件的数据源,
但是,当内存中数据源都不处于活动状态时,将这些数据源定义为默认值。default
dev
production
以下代码清单演示了如何实现相同的配置和
使用类而不是 XML 进行集成测试:@Configuration
@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("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("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new 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();
}
}
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
在此变体中,我们将 XML
配置拆分为四个独立的类:@Configuration
-
TransferServiceConfig
:通过使用 获取依赖注入。dataSource
@Autowired
-
StandaloneDataConfig
:为适合 开发人员测试。dataSource
-
JndiDataConfig
:定义从生产中的 JNDI 检索到的 环境。dataSource
-
DefaultDataConfig
:为默认嵌入式数据库定义一个,以防万一 配置文件处于活动状态。dataSource
与基于 XML 的配置示例一样,我们仍然使用
进行注释,但这次我们通过
使用注释。测试类本身的主体仍然存在
完全没有变化。TransferServiceTest
@ActiveProfiles("dev")
@ContextConfiguration
通常情况下,在多个测试类中使用一组配置文件
在给定项目中。因此,为了避免重复声明注释,可以在基类和子类上声明一次
自动从基类继承配置。在
以下示例,声明(以及其他注释)
已移至抽象超类:@ActiveProfiles
@ActiveProfiles
@ActiveProfiles
@ActiveProfiles
AbstractIntegrationTest
从 Spring Framework 5.3
开始,测试配置也可以继承自 enclosed
类。有关详细信息@Nested 请参阅测试类配置。
|
@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
}
}
@ActiveProfiles
还支持可用于
禁用活动配置文件的继承,如以下示例所示:inheritProfiles
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
此外,有时需要解析测试的活动配置文件 以编程方式而不是以声明方式 — 例如,基于:
-
当前操作系统。
-
测试是否在持续集成生成服务器上运行。
-
存在某些环境变量。
-
自定义类级批注的存在。
-
其他问题。
要以编程方式解析活动
Bean 定义概要文件,可以实现
自定义并使用 的属性进行注册。有关详细信息,请参阅相应的 javadoc。
以下示例演示如何实现和注册自定义:ActiveProfilesResolver
resolver
@ActiveProfiles
OperatingSystemActiveProfilesResolver
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends 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};
}
}
使用测试属性源进行上下文配置
Spring Framework 对具有
属性源的层次结构,并且可以使用特定于测试的集成测试进行配置
属性源。与类上使用的注释相反,您可以在测试中声明注释
类来声明测试属性文件或内联属性的资源位置。
这些测试属性源将添加到 for the loaded for the loaded for the annotated integration test
的集合中。@PropertySource
@Configuration
@TestPropertySource
PropertySources
Environment
ApplicationContext
您可以与SPI的任何实现一起使用,但不支持旧SPI的实现。 获得对合并测试属性源值的访问权限的实现
通过 中的 和 方法。 |
声明测试属性源
可以使用 的 or
属性来配置测试属性文件。locations
value
@TestPropertySource
支持传统属性文件格式和基于
XML 的属性文件格式,例如,或
."classpath:/com/example/test.properties"
"file:///path/to/file.xml"
每条路径都被解释为
Spring 。普通路径(例如,)被视为相对于包的类路径资源
其中定义了测试类。以斜杠开头的路径被视为
绝对类路径资源(例如:)。一条路径
引用 URL(例如,以 、 或 为前缀的路径)是
使用指定的资源协议加载。不允许使用资源位置通配符(例如
):每个位置的计算结果必须恰好为一个或资源。Resource
"test.properties"
"/org/example/test.xml"
classpath:
file:
http:
*/.properties
.properties
.xml
以下示例使用测试属性文件:
@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 | 使用键值语法的两种变体设置两个属性。 |
从
Spring Framework 5.2 开始,可以用作可重复的注解。
这意味着您可以在单个
测试类,后面的注解中的 和会覆盖以前注解中的注释。 此外,您可以在一个测试类上声明多个组合注释,每个注释都是
使用 进行元注释,所有这些声明都将有助于您的测试属性源。 直接呈现的注释始终优先于
元呈现注释。换言之,and from 一个直接存在的注解将覆盖 and from 一个用作
元注释。 |
默认属性文件检测
If 声明为空注解(即,没有显式
或属性的值),尝试检测
相对于声明注释的类的缺省属性文件。例如
如果带批注的测试类是 ,则相应的默认属性
文件是 。如果无法检测到默认值,则抛出 。@TestPropertySource
locations
properties
com.example.MyTest
classpath:com/example/MyTest.properties
IllegalStateException
优先
测试属性的优先级高于操作系统的
环境、Java 系统属性或应用程序添加的属性源
使用或以编程方式以声明方式。因此,测试属性可以
用于有选择地覆盖从系统和应用程序属性加载的属性
来源。此外,内联属性的优先级高于加载的属性
从资源位置。但请注意,通过@DynamicPropertySource
注册的属性具有
优先级高于通过 加载的优先级。@PropertySource
@TestPropertySource
在下一个示例中,和属性以及
中定义的任何属性将覆盖系统中定义的任何同名属性
和应用程序属性源。此外,如果文件定义
和属性的条目,这些属性被内联的
使用该特性声明的属性。以下示例演示如何
要同时指定文件和内联属性,请执行以下操作:timezone
port
"/test.properties"
"/test.properties"
timezone
port
properties
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
// class body...
}
继承和重写测试属性源
@TestPropertySource
支持布尔值和属性,这些属性表示属性文件和内联的资源位置
应继承超类声明的属性。两个标志的默认值
是。这意味着测试类继承位置和内联属性
由任何超类声明。具体而言,位置和内联属性
测试类将追加到超类声明的位置和内联属性中。
因此,子类可以选择扩展位置和内联属性。注意
稍后出现的属性会遮蔽(即覆盖)同名属性
出现较早。此外,上述优先规则适用于继承的
测试属性源。inheritLocations
inheritProperties
true
如果 中的 or 属性是
设置为 ,分别为测试类的位置或内联属性
影子,并有效地替换超类定义的配置。inheritLocations
inheritProperties
@TestPropertySource
false
从 Spring Framework
5.3 开始,测试配置也可以继承自 enclosed
类。有关详细信息@Nested 请参阅测试类配置。
|
在下一个示例中,仅使用文件作为测试属性源来加载
for。相反,通过使用 和 文件作为测试属性源位置来加载 for。以下示例演示如何定义
子类及其超类中的属性(通过使用文件):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 {
// ...
}
在下一个示例中,仅使用
inlined 属性。相比之下,for 是
使用 inlined 和 properties 加载。以下示例演示如何
使用内联属性定义子类及其超类中的属性:ApplicationContext
BaseTest
key1
ApplicationContext
ExtendedTest
key1
key2
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
使用动态属性源进行上下文配置
从 Spring Framework 5.2.5
开始,TestContext 框架通过注解提供对动态属性的支持。此注解可用于
需要将具有动态值的属性添加到 for for
集成测试。@DynamicPropertySource
PropertySources
Environment
ApplicationContext
注释及其支持的基础结构是
最初设计为允许基于 Testcontainers 的测试中的属性轻松公开给
Spring 集成测试。但是,此功能也可以与任何形式的
其生命周期维护在测试的 . |
与在类级别应用的 @TestPropertySource
注释相反,必须应用
更改为接受单个参数的方法,该参数是
用于向 .值是动态的,通过
a 仅在解析属性时调用。通常,方法
引用用于提供值,如以下示例所示,该示例使用
Testcontainers 项目来管理 Spring 之外的 Redis 容器。创建托管 Redis 容器的 IP 地址和端口
可通过 和 属性用于测试中的组件。这些属性可以通过 Spring 的抽象访问,也可以直接注入到
Spring 管理的组件中——例如,分别是 via 和
。@DynamicPropertySource
static
DynamicPropertyRegistry
Environment
Supplier
ApplicationContext
ApplicationContext
redis.host
redis.port
Environment
@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 ...
}
加载一个WebApplicationContext
指示 TestContext 框架加载
标准,您可以使用 注释相应的测试类。WebApplicationContext
ApplicationContext
@WebAppConfiguration
on 测试类的存在指示
TestContext
框架 (TCF),应该为您的 (WAC) 加载
集成测试。在后台,TCF 确保
创建并提供给测试的 WAC。默认情况下,your 的基本资源路径设置为 。这被解释为路径相对
到 JVM 的根目录(通常是项目的路径)。如果您熟悉
Maven 项目中 Web 应用程序的目录结构,您知道这是 WAR 根目录的默认位置。如果需要
覆盖此默认值时,您可以为注释提供备用路径(例如,)。如果您愿意
从类路径而不是文件系统引用基本资源路径,您可以使用
Spring 的前缀。@WebAppConfiguration
WebApplicationContext
MockServletContext
MockServletContext
src/main/webapp
src/main/webapp
@WebAppConfiguration
@WebAppConfiguration("src/test/webapp")
classpath:
请注意,Spring
对实现的测试支持是相当的
支持标准实现。当使用 进行测试时,您可以自由声明 XML 配置文件、Groovy 脚本、
或使用 .您也可以免费使用
任何其他测试注释,例如 、 、 、 等。WebApplicationContext
ApplicationContext
WebApplicationContext
@Configuration
@ContextConfiguration
@ActiveProfiles
@TestExecutionListeners
@Sql
@Rollback
本节中的其余示例显示了
加载 .下面的示例演示 TestContext
框架对约定优先于配置的支持:WebApplicationContext
@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 {
//...
}
如果在未指定资源的情况下对测试类进行注释
基本路径,则资源路径实际上默认为 。同样地
如果声明时没有指定 resource , component 或 context ,Spring 会尝试检测
使用约定进行配置(即,在同一包中
作为类或静态嵌套类)。@WebAppConfiguration
file:src/main/webapp
@ContextConfiguration
locations
classes
initializers
WacTests-context.xml
WacTests
@Configuration
下面的示例演示如何使用
显式声明资源基路径,并使用 XML 声明 XML 资源位置:@WebAppConfiguration
@ContextConfiguration
@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 {
//...
}
将此示例中的注释与上一个示例进行对比。
为了提供全面的
Web 测试支持,TestContext 框架默认启用了 WebContext 框架。当针对 进行测试时,此 TestExecutionListener
使用 Spring Web 的 before 设置默认线程本地状态
每个测试方法并创建一个 、 和
a 基于配置了 的基本资源路径。 也保证了 and 可以注入到测试实例中,
并且,一旦测试完成,它就会清理线程本地状态。ServletTestExecutionListener
WebApplicationContext
RequestContextHolder
MockHttpServletRequest
MockHttpServletResponse
ServletWebRequest
@WebAppConfiguration
ServletTestExecutionListener
MockHttpServletResponse
ServletWebRequest
一旦你加载了测试,你可能会发现你
需要与网络模拟进行交互 - 例如,设置您的测试夹具或
调用 Web 组件后执行断言。以下示例显示了
模拟可以自动连接到测试实例中。请注意,和都缓存在测试套件中,而其他模拟则缓存在测试套件中。
每个测试方法由 .WebApplicationContext
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;
//...
}
上下文缓存
一旦 TestContext
框架加载一个(或 )
对于测试,该上下文将被缓存并重用于声明
在同一测试套件中具有相同的唯一上下文配置。了解如何缓存
工作,了解“唯一”和“测试套件”的含义很重要。ApplicationContext
WebApplicationContext
可以通过配置组合进行唯一标识
用于加载它的参数。因此,配置的独特组合
parameters 用于生成一个键,在该键下缓存上下文。The TestContext
框架使用以下配置参数来构建上下文缓存密钥:ApplicationContext
-
locations
(从@ContextConfiguration
) -
classes
(从@ContextConfiguration
) -
contextInitializerClasses
(从@ContextConfiguration
) -
contextCustomizers
(from ) – 这包括 Spring Boot 的方法和各种功能 测试支持,例如 和 。ContextCustomizerFactory
@DynamicPropertySource
@MockBean
@SpyBean
-
contextLoader
(从@ContextConfiguration
) -
parent
(从@ContextHierarchy
) -
activeProfiles
(从@ActiveProfiles
) -
propertySourceLocations
(从@TestPropertySource
) -
propertySourceProperties
(从@TestPropertySource
) -
resourceBasePath
(从@WebAppConfiguration
)
例如,如果为 的 (或 )
属性指定 ,则 TestContext 框架
加载相应的内容并将其存储在上下文缓存中
在仅基于这些位置的密钥下。因此,if 还为其位置定义(显式或
隐式通过继承)但未定义,不同的,不同的活动配置文件,不同的上下文初始值设定项,不同的
测试属性源或不同的父上下文,则两个测试类共享相同的属性。这意味着加载应用程序的设置成本
上下文只发生一次(每个测试套件),后续测试执行很多
更快。TestClassA
{"app-config.xml",
"test-config.xml"}
locations
value
@ContextConfiguration
ApplicationContext
static
TestClassB
{"app-config.xml",
"test-config.xml"}
@WebAppConfiguration
ContextLoader
ApplicationContext
测试套件和分叉进程
Spring
TestContext 框架将应用程序上下文存储在静态缓存中。这
意味着上下文实际上存储在变量中。换言之,如果
测试在单独的进程中运行,静态缓存在每次测试之间被清除
执行,这有效地禁用了缓存机制。 若要从缓存机制中受益,所有测试必须在同一进程或测试中运行
套房。这可以通过在 IDE 中作为一个组执行所有测试来实现。同样地
使用 Ant、Maven 或 Gradle 等构建框架执行测试时,它是
确保构建框架不会在测试之间分叉。例如
如果 Maven Surefire 插件的 |
上下文缓存的大小受默认最大大小
32 的限制。每当
达到最大大小时,使用最近最少使用的 (LRU) 逐出策略进行逐出,并且
关闭陈旧的上下文。您可以从命令行或构建中配置最大大小
脚本,方法是设置名为 的 JVM 系统属性。作为
或者,您可以通过 SpringProperties
机制设置相同的属性。spring.test.context.cache.maxSize
由于在给定的测试套件中加载了大量应用程序上下文可以
导致套件需要不必要的长时间才能运行,这通常有利于
确切地知道已经加载和缓存了多少个上下文。查看统计信息
底层上下文缓存中,可以将日志记录类别的日志级别设置为 。org.springframework.test.context.cache
DEBUG
在极少数情况下,测试会损坏应用程序上下文并需要重新加载
(例如,通过修改 Bean 定义或应用程序对象的状态),您可以
可以注释你的测试类或测试方法(参见 Spring Testing 中的讨论
注释)。这指示 Spring 从缓存中删除上下文并重新构建
在运行需要相同应用程序的下一个测试之前的应用程序上下文
上下文。请注意,默认情况下启用的 和
提供对注释的支持。@DirtiesContext
@DirtiesContext
@DirtiesContext
DirtiesContextBeforeModesTestExecutionListener
DirtiesContextTestExecutionListener
ApplicationContext
生命周期和控制台日志记录
当您需要调试使用
Spring TestContext Framework 执行的测试时,它可以是
用于分析控制台输出(即输出到 和 流)。某些构建工具和 IDE 能够将控制台输出与给定的
测试;但是,某些控制台输出无法轻松与给定测试相关联。 关于由
Spring Framework 本身或组件触发的控制台日志记录
注册,了解 Spring TestContext 框架在
测试套件。 for
测试通常在测试实例出现时加载
类正在准备中 — 例如,对测试实例的字段执行依赖注入。这意味着在
的初始化通常不能与
个别测试方法。但是,如果上下文在紧接
执行测试方法 根据 可以通过以下情况之一关闭测试。
如果在特定测试后根据语义关闭上下文
方法,IDE 或构建工具可能会将控制台日志记录与
个别测试方法。如果上下文根据语义关闭
在测试类之后,在关闭期间触发的任何控制台日志记录都不能与单个测试方法相关联。同样,任何
在关闭阶段通过 JVM 关闭钩子触发的控制台日志记录不能
与单个测试方法相关联。 当通过
JVM 关闭钩子关闭 Spring 时,会执行回调
在关闭阶段,在名为 的线程上执行。所以
如果您希望禁用关闭时触发的控制台日志记录
通过 JVM shutdown 钩子,您可以在日志记录中注册自定义过滤器
允许您忽略该线程启动的任何日志记录的框架。 |
上下文层次结构
当编写依赖于加载的
Spring 的集成测试时,它是
通常足以针对单个上下文进行测试。但是,有时确实如此
对实例层次结构进行测试是有益的,甚至是必要的。例如,如果您正在开发 Spring MVC Web 应用程序,您通常
有一个由 Spring 加载的根和一个
由 Spring 的 .这会导致
父子上下文层次结构,其中共享组件和基础结构配置
在根上下文中声明,并在子上下文中由特定于 Web 的使用
组件。另一个用例可以在 Spring Batch 应用程序中找到,您经常在
具有为共享批处理基础结构提供配置的父上下文和
用于配置特定批处理作业的子上下文。ApplicationContext
ApplicationContext
WebApplicationContext
ContextLoaderListener
WebApplicationContext
DispatcherServlet
您可以通过声明上下文来编写使用上下文层次结构的集成测试
带有注释的配置,可以在单个测试类上
或在测试类层次结构中。如果在多个类上声明上下文层次结构
在测试类层次结构中,还可以合并或覆盖上下文配置
用于上下文层次结构中的特定命名级别。合并
层次结构中给定的级别,即配置资源类型(即 XML 配置
文件或组件类)必须一致。否则,这是完全可以接受的
在上下文层次结构中使用不同的资源类型配置不同的级别。@ContextHierarchy
本节中其余基于 JUnit Jupiter 的示例显示了通用配置 需要使用上下文层次结构的集成测试方案。
ControllerIntegrationTests
表示
Spring MVC Web 应用程序通过声明由两个级别组成的上下文层次结构,
一个用于根(使用该类加载),一个用于调度程序 Servlet(使用该类加载)。自动连接到测试实例的是子上下文(即
层次结构中的最低上下文)。以下列表显示了此配置方案:WebApplicationContext
TestAppConfig
@Configuration
WebApplicationContext
WebConfig
@Configuration
WebApplicationContext
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
@Autowired
WebApplicationContext wac;
// ...
}
此示例中的测试类在测试类中定义上下文层次结构
等级制度。 声明 Spring 驱动的 Web 应用程序中根的配置。但是请注意,这并没有声明
.因此,子类可以选择性地参与上下文层次结构或遵循
的标准语义。 并通过以下方式扩展和定义上下文层次结构
用。结果是加载了三个应用程序上下文(一个
对于每个声明,并加载应用程序上下文
基于中的配置,设置为每个
为具体子类加载的上下文。下面的清单显示了这一点
配置场景:AbstractWebTests
WebApplicationContext
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 {}
此示例中的类显示如何使用命名层次结构级别来合并
上下文层次结构中特定级别的配置。 定义两个级别
在层次结构中,和 . 扩展和指示
Spring TestContext Framework 通过确保 中的属性中声明的名称都是 .结果是三个应用程序上下文
加载:一个用于 ,一个用于 ,一个用于 。与前面的示例一样,
应用程序上下文加载自 设置为父上下文
从 和 加载的上下文。
以下列表显示了此配置方案:BaseTests
parent
child
ExtendedTests
BaseTests
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 {}
与前面的示例相比,此示例演示了如何覆盖
通过将标志设置为 来配置上下文层次结构中的给定命名级别。因此,
的应用程序上下文 仅从 和 加载
将其父级设置为从 加载的上下文。以下列表
显示了以下配置方案: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 {}
在上下文层次结构中弄脏上下文
如果在测试中使用,其上下文配置为
上下文层次结构,可以使用该标志来控制上下文缓存的方式
被清除。有关详细信息,请参阅 Spring Testing
Annotations 中的讨论和 javadoc
@DirtiesContext 。@DirtiesContext hierarchyMode @DirtiesContext
|
3.5.7. 测试夹具的依赖注入
当您使用 (由
default),测试实例的依赖项是从
您配置了应用程序上下文或与之相关的应用程序上下文
附注。您可以使用 setter 注入、场注入或两者兼而有之,具体取决于
选择哪些注释,以及是否将它们放在 setter 方法或字段上。
如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入
(参见 Dependency Injection with
SpringExtension
)。为了与 Spring 基于注解的
注入支持,您还可以使用 Spring 的注解或 JSR-330 的注解进行字段和 setter 注入。DependencyInjectionTestExecutionListener
@ContextConfiguration
@Autowired
@Inject
对于
JUnit Jupiter 以外的测试框架,TestContext 框架不会
参与测试类的实例化。因此,对构造函数使用 or
对测试类没有影响。@Autowired @Inject |
尽管在生产代码中不鼓励字段注入,但字段注入是
实际上在测试代码中很自然。造成这种差异的理由是,您将
永远不要直接实例化你的测试类。因此,没有必要能够
在测试类上调用构造函数或 setter 方法。public |
因为用于执行自动接线
type,如果您有多个相同类型的 Bean 定义,则不能依赖此定义
针对这些特定 bean 的方法。在这种情况下,您可以在
结合。您也可以选择与 结合使用。或者,如果您的测试类可以访问其 ,则
可以通过使用(例如)调用
来执行显式查找。@Autowired
@Autowired
@Qualifier
@Inject
@Named
ApplicationContext
applicationContext.getBean("titleRepository",
TitleRepository.class)
如果您不希望将依赖注入应用于测试实例,请不要注释
字段或 setter 方法。或者,您可以禁用
通过显式配置类来注入依赖项,并从侦听器列表中省略。@Autowired
@Inject
@TestExecutionListeners
DependencyInjectionTestExecutionListener.class
考虑测试类的方案,如“目标”部分所述。接下来的两个代码清单演示了
使用 on 字段和 setter 方法。应用程序上下文配置
显示在所有示例代码列表之后。HibernateTitleRepository
@Autowired
以下代码清单中的依赖关系注入行为并非特定于 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);
}
}
或者,可以将类配置为用于
setter 注入,如
遵循:@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);
}
}
前面的代码清单使用批注引用的同一
XML 上下文文件(即 )。以下
显示以下配置:@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 提供的测试基类扩展,而该基类恰好在其 setter 方法之一上使用,则可能有多个受影响的
bean
在应用程序上下文中定义的类型(例如,多个 Bean)。在
在这种情况下,您可以重写 setter 方法并使用注解
指示一个特定的目标 bean,如下所示(但请确保委托给被覆盖的
方法):
爪哇岛
Kotlin
指定的限定符值指示要注入的特定
bean,
将类型匹配集缩小到特定 Bean。其值与相应定义中的声明匹配。Bean 名称
用作回退限定符值,因此也可以有效地指向特定的
bean 按名称存在(如前所示,假设是 bean )。 |
3.5.8. 测试请求和会话范围的 Bean
Spring 支持请求范围和会话范围 Bean,您可以测试请求范围和会话范围 bean,请按照以下步骤操作:
-
通过注释测试,确保为测试加载了 a 类。
WebApplicationContext
@WebAppConfiguration
-
将模拟请求或会话注入到测试实例中并准备测试 根据需要固定。
-
调用从配置的 Web 组件(使用依赖项注入)中检索到的 Web 组件。
WebApplicationContext
-
对模拟执行断言。
下一个代码片段显示了登录用例的
XML 配置。请注意,Bean 依赖于请求范围的 Bean。此外,通过使用 SpEL 表达式实例化
从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望
通过 TestContext 框架管理的 mock 配置这些请求参数。
以下列表显示了此用例的配置:userService
loginAction
LoginAction
<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>
在 中,我们同时注入 (即主语
test) 并放入我们的测试实例中。在我们的测试方法中,我们通过在
提供的 .当在我们的 上调用该方法时,我们确信用户服务可以访问当前(即我们刚刚
设置参数)。然后,我们可以根据已知的结果执行断言
用户名和密码的输入。下面的清单显示了如何执行此操作:RequestScopedBeanTests
UserService
MockHttpServletRequest
requestScope()
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
}
}
以下代码片段类似于我们之前看到的请求范围的代码片段
豆。但是,这一次,Bean 依赖于会话范围的 Bean。请注意,Bean 是使用
从当前 HTTP 会话中检索主题的 SpEL 表达式。在我们的测试中,我们
需要在 TestContext 框架管理的模拟会话中配置一个主题。这
以下示例演示如何执行此操作:userService
userPreferences
UserPreferences
<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
}
}
3.5.9. 事务管理
在 TestContext 框架中,事务由
,默认情况下会配置,即使您不这样做也是如此
在测试类上显式声明。启用对
但是,您必须在加载了语义的 bean 中配置一个 bean(进一步
详情见下文)。此外,您必须在测试的类或方法级别声明 Spring 的注解。TransactionalTestExecutionListener
@TestExecutionListeners
PlatformTransactionManager
ApplicationContext
@ContextConfiguration
@Transactional
测试管理的事务
测试管理的事务是使用
以声明方式管理的事务,或者是以编程方式使用 (稍后介绍) 的事务。您不应将此类事务与 Spring
管理的事务混淆
事务(由 Spring 在 加载的
tests)或应用程序管理的事务(以编程方式管理的事务
由测试调用的应用程序代码)。Spring 托管和应用程序托管
事务通常参与测试管理的事务。但是,您应该使用
如果 Spring 管理的事务或应用程序管理的事务配置了任何
除 OR 以外的传播类型(有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListener
TestTransaction
ApplicationContext
REQUIRED
SUPPORTS
抢占式超时和测试管理事务
在测试框架中使用任何形式的抢占式超时,必须小心 与 Spring 的测试管理事务结合使用。 具体来说,Spring
的测试支持将事务状态绑定到当前线程(通过
变量),然后调用当前测试方法。如果
测试框架在新线程中调用当前测试方法,以支持
抢占式超时,在当前测试方法中执行的任何操作都不会
在测试管理的事务中调用。因此,任何此类行动的结果
不会随测试管理的事务一起回滚。相反,这样的行为
将提交到持久性存储(例如,关系数据库)中,甚至
尽管测试管理的事务被 Spring
正确回滚。 发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
对测试方法进行注释会导致测试在
默认情况下,事务在测试完成后自动回滚。
如果测试类使用 ,则该类中的每个测试方法都带有注释
层次结构在事务中运行。未注释的测试方法(在类或方法级别)不会在事务中运行。注意
测试生命周期方法(例如,方法)不支持
用 JUnit Jupiter 的 、 等注释。此外,测试
已批注,但该属性已设置为或未在事务中运行。@Transactional
@Transactional
@Transactional
@Transactional
@BeforeAll
@BeforeEach
@Transactional
propagation
NOT_SUPPORTED
NEVER
属性 | 支持测试管理的事务 |
---|---|
|
是的 |
|
仅且受支持 |
|
不 |
|
不 |
|
不 |
|
否:改用 |
|
否:改用 |
方法级生命周期方法(例如,用
JUnit Jupiter 的 or 注释的方法)在测试管理的事务中运行。另一方面
手、套件级和类级生命周期方法,例如,用
JUnit Jupiter 的 or 和用 TestNG 的 、 、 或 — 注释的方法不在
测试管理的事务。 如果需要在套件级别或类级别的生命周期方法中运行代码
事务中,您可能希望注入相应的
你的测试类,然后将其与 for programmatic 一起使用
事务管理。 |
请注意,AbstractTransactionalJUnit4SpringContextTests
和 AbstractTransactionalTestNGSpringContextTests
已针对类级别的事务支持进行了预配置。
以下示例演示了为
a 基于休眠: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"));
}
}
如事务回滚和提交行为中所述,没有必要
在方法运行后清理数据库,因为对
数据库由 自动回滚。createUser()
TransactionalTestExecutionListener
事务回滚和提交行为
默认情况下,测试事务将在完成
测试;但是,事务提交和回滚行为可以通过声明方式进行配置
通过 和 注释。有关详细信息,请参阅注释支持部分中的相应条目。@Commit
@Rollback
程序化事务管理
您可以使用静态
中的方法。例如,您可以在 test 中使用
方法、方法之前和方法之后开始或结束当前测试托管的方法
事务或配置当前测试管理的事务以进行回滚或提交。
每当启用时,都会自动提供对 的支持。TestTransaction
TestTransaction
TestTransaction
TransactionalTestExecutionListener
下面的示例演示了
的一些功能。请参阅
javadoc for TestTransaction
了解更多详情。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"));
}
}
在事务外部运行代码
有时,您可能需要在事务测试之前或之后运行某些代码
方法,但在事务上下文之外 — 例如,验证初始
运行测试或验证预期事务提交之前的数据库状态
测试运行后的行为(如果测试配置为提交事务)。 支持此类场景的 AND
注解。您可以使用以下方法之一对测试类中的任何方法或测试接口中的任何默认方法进行注释
注解,并确保之前
事务方法或事务方法在适当的时间运行之后。TransactionalTestExecutionListener
@BeforeTransaction
@AfterTransaction
void
void
TransactionalTestExecutionListener
任何 before 方法(例如用 JUnit Jupiter
注释的方法)
任何 after 方法(例如用 JUnit Jupiter 注释的方法)都是
在事务中运行。此外,对于未配置为在
交易。@BeforeEach @AfterEach @BeforeTransaction @AfterTransaction
|
配置事务管理器
TransactionalTestExecutionListener
期望 Bean 是
在 Spring 中为测试定义。如果有多个实例
在测试中,你可以声明一个
通过使用 或 限定符,或者可以由类实现。查阅 javadoc
for TestContextTransactionUtils.retrieveTransactionManager()
以获取有关
用于在测试的
.PlatformTransactionManager
ApplicationContext
PlatformTransactionManager
ApplicationContext
@Transactional("myTxMgr")
@Transactional(transactionManager
=
"myTxMgr")
TransactionManagementConfigurer
@Configuration
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
}
}
在测试 ORM
代码时避免误报
当您测试操作 Hibernate 会话或 JPA 状态的应用程序代码时 持久性上下文,请确保刷新测试方法中的基础工作单元 运行该代码。未能刷新基础工作单元可能会产生错误 positives:您的测试已通过,但相同的代码在实时生产中引发异常 环境。请注意,这适用于任何维护内存单元的 ORM 框架 的工作。在以下基于 Hibernate 的示例测试用例中,有一个方法演示了 误报,另一种方法会正确暴露刷新 会期: 爪哇岛
Kotlin
以下示例显示了 JPA 的匹配方法: 爪哇岛
Kotlin
|
测试 ORM
实体生命周期回调
与在测试 ORM 代码时避免误报的说明类似,如果您的应用程序使用实体生命周期回调(也 称为实体侦听器),请确保刷新测试中的基础工作单元 运行该代码的方法。未能刷新或清除基础工作单元可能会 导致某些生命周期回调未被调用。 例如,在使用
JPA、、 和回调时
除非在实体
已保存或更新。同样,如果实体已附加到当前工作单元
(与当前持久性上下文相关联),尝试重新加载实体将
不会导致回调,除非在
尝试重新加载实体。 以下示例演示如何刷新
以确保在持久化实体时调用回调。具有
已为
例。 爪哇岛
Kotlin
请参阅 Spring Framework 测试套件中的 JpaEntityListenerTests,了解使用所有 JPA 生命周期回调的工作示例。 |
3.5.10. 执行 SQL 脚本
在针对关系数据库编写集成测试时,通常对
运行 SQL 脚本以修改数据库结构或将测试数据插入到表中。该模块支持初始化嵌入式或现有数据库
通过在加载 Spring 时执行 SQL 脚本。请参阅嵌入式数据库支持和使用
嵌入式数据库了解详细信息。spring-jdbc
ApplicationContext
尽管在加载 时初始化一次数据库进行测试非常有用,但有时必须能够修改
集成测试期间的数据库。以下各节说明如何运行 SQL
在集成测试期间以编程方式和声明方式编写脚本。ApplicationContext
以编程方式执行 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 脚本的解析和运行方式,可能适合
您的需求比后面描述的其他一些替代方案更好。请参阅 javadoc 了解个人
方法。ScriptUtils
ScriptUtils
ResourceDatabasePopulator
提供基于对象的 API,用于以编程方式填充,
使用外部中定义的 SQL 脚本初始化或清理数据库
资源。 提供用于配置字符的选项
编码、语句分隔符、注释分隔符和错误处理标志在以下情况下使用
解析和运行脚本。每个配置选项都有一个合理的
默认值。请参阅 javadoc
有关默认值的详细信息。要运行中配置的脚本,可以调用以下任一方法
针对 或 方法运行 populator
以针对 .以下示例
指定测试架构和测试数据的 SQL 脚本,将语句分隔符设置为 ,然后针对 :ResourceDatabasePopulator
ResourceDatabasePopulator
populate(Connection)
java.sql.Connection
execute(DataSource)
javax.sql.DataSource
@@
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
}
请注意,内部委托给 for 解析
并运行 SQL 脚本。同样,AbstractTransactionalJUnit4SpringContextTests
和 AbstractTransactionalTestNGSpringContextTests
中的方法在内部使用 a 来运行 SQL 脚本。请参阅 Javadoc 中
各种方法以获取更多详细信息。ResourceDatabasePopulator
ScriptUtils
executeSqlScript(..)
ResourceDatabasePopulator
executeSqlScript(..)
使用 @Sql 以声明方式执行 SQL 脚本
除了前面提到的以编程方式运行
SQL 脚本的机制之外,
您可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。
具体来说,可以将测试类或测试方法的注释声明为
配置单个 SQL 语句或应为 SQL 脚本的资源路径
在集成测试方法之前或之后针对给定数据库运行。对 的支持由
提供,默认情况下处于启用状态。@Sql
@Sql
SqlScriptsTestExecutionListener
默认情况下,方法级声明会重写类级声明。如
但是,此行为可以按测试类或每个
测试方法通过 .有关详细信息,请参阅使用 @SqlMergeMode
合并和覆盖配置。@Sql @SqlMergeMode |
路径资源语义
每条路径都被解释为
Spring 。普通路径(例如,)被视为相对于
定义测试类。以斜杠开头的路径被视为绝对路径
类路径资源(例如,)。引用
URL(例如,以 , , 为前缀的路径)是使用
指定的资源协议。Resource
"schema.sql"
"/org/example/schema.sql"
classpath:
file:
http:
下面的示例演示如何在类级别和方法级别使用
在基于 JUnit Jupiter 的集成测试类中:@Sql
@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
}
}
默认脚本检测
如果未指定 SQL
脚本或语句,则会尝试检测脚本,具体取决于声明的位置。如果无法检测到默认值,则抛出
。default
@Sql
IllegalStateException
-
类级声明:如果带注释的测试类是 , 对应的默认脚本是 。
com.example.MyTest
classpath:com/example/MyTest.sql
-
方法级声明:如果带注释的测试方法已命名并且是 在类中定义,对应的默认脚本是 。
testMethod()
com.example.MyTest
classpath:com/example/MyTest.testMethod.sql
声明多个集合@Sql
如果需要为给定的测试类或测试配置多组
SQL 脚本
方法,但具有不同的语法配置、不同的错误处理规则,或者
每组不同的执行阶段,可以声明多个 的实例。跟
Java 8,可以用作可重复的注解。否则,可以将注释用作显式容器,以声明
的多个实例。@Sql
@Sql
@SqlGroup
@Sql
以下示例显示了如何在
Java 8 中用作可重复的注解:@Sql
@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
}
在前面示例中介绍的方案中,该脚本使用
单行注释的不同语法。test-schema.sql
以下示例与前面的示例相同,只是声明在
中组合在一起。在 Java 8 及更高版本中,使用 是可选的,但您可能需要使用 以兼容
其他 JVM 语言,例如 Kotlin。@Sql
@SqlGroup
@SqlGroup
@SqlGroup
@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
}
脚本执行阶段
默认情况下,SQL
脚本在相应的测试方法之前运行。但是,如果
您需要在测试方法之后运行一组特定的脚本(例如,要清理
up database state),您可以使用 中的属性作为
以下示例显示: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
}
请注意,和分别是从 和
静态导入的。ISOLATED
AFTER_TEST_METHOD
Sql.TransactionMode
Sql.ExecutionPhase
脚本配置@SqlConfig
您可以使用注释来配置脚本解析和错误处理。
当声明为集成测试类的类级注释时,用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候
使用注释的属性直接声明,用作在封闭注释中声明的 SQL 脚本的本地配置。中的每个属性都有一个隐式默认值,即
记录在相应属性的 javadoc 中。由于定义的规则
Java 语言规范中的注解属性,不幸的是,它不是
可以为注释属性赋值。因此,为了
支持覆盖继承的全局配置,属性具有
显式默认值 (for Strings)、(for arrays) 或 (for
枚举)。此方法允许有选择地重写本地声明
来自全局声明的单个属性,通过提供其他值
比 、 或 。全局属性在以下情况下被继承
局部属性不提供除 、 或 以外的显式值。因此,显式本地配置将覆盖全局配置。@SqlConfig
@SqlConfig
config
@Sql
@SqlConfig
@Sql
@SqlConfig
null
@SqlConfig
""
{}
DEFAULT
@SqlConfig
@SqlConfig
""
{}
DEFAULT
@SqlConfig
@SqlConfig
""
{}
DEFAULT
提供的配置选项 和 等效于
由 和 支持,但 是它们的超集
由 XML 命名空间元素提供。请参阅 javadoc 的
@Sql
中的各个属性,有关详细信息,@SqlConfig
。@Sql
@SqlConfig
ScriptUtils
ResourceDatabasePopulator
<jdbc:initialize-database/>
@Sql
事务管理
默认情况下,推断所需的事务
使用 配置的脚本的语义。具体而言,运行 SQL 脚本
没有事务,在现有的 Spring 管理事务中(例如,一个
事务由 for 测试管理,并用 ) 注释,或在独立事务中管理,具体取决于配置的值
的属性和测试中 a 的存在。最低限度,
但是,测试的
.SqlScriptsTestExecutionListener
@Sql
TransactionalTestExecutionListener
@Transactional
transactionMode
@SqlConfig
PlatformTransactionManager
ApplicationContext
javax.sql.DataSource
ApplicationContext
如果用于检测 和
并推断事务语义的算法不符合您的需求,
您可以通过设置 的 和 属性来指定显式名称。此外,您可以控制事务传播
行为,通过设置 的属性(例如,是否
脚本应在独立事务中运行)。虽然对所有
事务管理支持的选项超出了此范围
参考手册、javadoc for @SqlConfig
和
SqlScriptsTestExecutionListener
提供了详细信息,以下示例显示了一个典型的测试场景
使用 JUnit Jupiter
和事务测试:SqlScriptsTestExecutionListener
DataSource
PlatformTransactionManager
dataSource
transactionManager
@SqlConfig
transactionMode
@SqlConfig
@Sql
@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.");
}
}
请注意,在方法
运行,因为对数据库所做的任何更改(无论是在测试方法中还是在脚本中)都会由 (请参阅事务管理
详细信息)。usersTest()
/test-data.sql
TransactionalTestExecutionListener
合并和覆盖配置@SqlMergeMode
从 Spring Framework
5.2 开始,可以将方法级声明与
类级声明。例如,这允许您提供
数据库模式或一些常见的测试数据,每个测试类一次,然后提供额外的,
每个测试方法的用例特定测试数据。要启用合并,请对任一内容进行注释
带有 的测试类或测试方法。禁用合并
特定的测试方法(或特定的测试子类),可以切换回默认模式
通过。有关示例和更多详细信息,请参阅@SqlMergeMode
注释文档部分。@Sql
@Sql
@SqlMergeMode(MERGE)
@SqlMergeMode(OVERRIDE)
3.5.11. 并行测试执行
Spring Framework 5.0 引入了对在 使用 Spring TestContext Framework 时的单个 JVM。一般来说,这意味着大多数 测试类或测试方法可以并行运行,而无需对测试代码进行任何更改 或配置。
有关如何设置并行测试执行的详细信息,请参阅 测试框架、构建工具或 IDE。 |
请记住,在测试套件中引入并发可能会导致 意外的副作用、奇怪的运行时行为以及间歇性失败的测试,或者 看似随机。因此,Spring 团队提供了以下一般准则 何时不并行运行测试。
如果测试出现以下情况,则不要并行运行测试:
-
使用 Spring Framework 的支持。
@DirtiesContext
-
使用 Spring Boot 或支持。
@MockBean
@SpyBean
-
使用 JUnit 4 的支持或任何测试框架功能 旨在确保测试方法按特定顺序运行。注意 但是,如果整个测试类并行运行,则这不适用。
@FixMethodOrder
-
更改共享服务或系统(如数据库、消息代理、 文件系统等。这适用于嵌入式和外部系统。
如果并行测试执行失败并出现异常,指出当前测试不再处于活动状态,这通常意味着已从其他线程中删除。 这可能是由于使用了
或由于自动逐出 .如果是罪魁祸首,你要么需要想办法
避免使用此类测试或从并行执行中排除此类测试。如果
已超过最大大小,可以增加最大大小
的缓存。有关详细信息,请参阅有关上下文缓存的讨论。 |
只有在以下情况下,才能在
Spring TestContext Framework 中并行执行测试
基础实现提供了一个复制构造函数,如
TestContext
的 javadoc。Spring 中使用的构造函数提供了这样一个构造函数。但是,如果您使用
提供自定义实现的第三方库,您需要
验证它是否适合并行测试执行。TestContext DefaultTestContext TestContext
|
3.5.12. TestContext框架支持类
本节介绍支持 Spring TestContext Framework 的各种类。
Spring JUnit 4 运行程序
Spring TestContext
Framework 通过自定义
runner(在 JUnit 4.12 或更高版本上受支持)。通过使用或较短的变体注释测试类,开发人员可以实现基于
JUnit 4 的标准单元和集成测试,以及
同时获得 TestContext 框架的好处,例如支持
加载应用程序上下文、测试实例的依赖注入、事务测试
方法执行等。如果要将 Spring TestContext 框架与
替代运行器(例如 JUnit 4 的运行器)或第三方运行器
(例如 ),您可以选择使用 Spring 对 JUnit 规则的支持。@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
Parameterized
MockitoJUnitRunner
下面的代码清单显示了将测试类配置为
使用自定义 Spring 运行:Runner
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
在前面的示例中,配置了一个空列表,以
禁用默认侦听器,否则需要
通过 进行配置。@TestExecutionListeners
ApplicationContext
@ContextConfiguration
Spring JUnit 4 规则
该软件包提供以下 JUnit
4 条规则(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4.rules
-
SpringClassRule
-
SpringMethodRule
SpringClassRule
是一个 JUnit,它支持 Spring 的类级特性
TestContext 框架,而是一个支持
Spring TestContext Framework 的实例级和方法级功能。TestRule
SpringMethodRule
MethodRule
与 相比,Spring 基于规则的
JUnit 支持具有以下优势
独立于任何实现,因此可以
与现有的替代运行器(例如 JUnit 4 )结合使用,或
第三方运行器(例如 )。SpringRunner
org.junit.runner.Runner
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...
}
}
JUnit 4 支持类
该软件包提供以下支持
基于 JUnit 4 的测试用例的类(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4
-
AbstractJUnit4SpringContextTests
-
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是一个抽象基测试类,它集成了
Spring TestContext 框架,在
JUnit 4 环境。扩展时,可以访问可用于执行显式执行的实例变量
Bean 查找或测试整个上下文的状态。ApplicationContext
AbstractJUnit4SpringContextTests
protected
applicationContext
AbstractTransactionalJUnit4SpringContextTests
是一个抽象的事务扩展,它为
JDBC 增加了一些便利功能
访问。此类需要在 中定义一个 Bean 和一个 Bean。当你
extend ,您可以访问一个实例变量,该变量可用于运行 SQL 语句以查询
数据库。您可以使用此类查询来确认之前和之后的数据库状态
运行与数据库相关的应用程序代码,Spring 确保此类查询在
与应用程序代码相同的事务的范围。当与
一个 ORM 工具,一定要避免误报。
如JDBC测试支持中所述,还提供了方便的方法
通过使用上述 .
此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractJUnit4SpringContextTests
javax.sql.DataSource
PlatformTransactionManager
ApplicationContext
AbstractTransactionalJUnit4SpringContextTests
protected
jdbcTemplate
AbstractTransactionalJUnit4SpringContextTests
JdbcTestUtils
jdbcTemplate
AbstractTransactionalJUnit4SpringContextTests
executeSqlScript(..)
DataSource
这些类是扩展的便利。如果你不想要你的测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类通过使用或 Spring 的
JUnit 规则。@RunWith(SpringRunner.class) |
适用于 JUnit Jupiter 的 SpringExtension
Spring TestContext
框架提供了与 JUnit Jupiter 测试的完全集成
框架,在 JUnit 5 中引入。通过使用 注释测试类,您可以实现基于 JUnit Jupiter 的标准单元
和集成测试,同时获得 TestContext 框架的好处,
比如支持加载应用上下文、测试实例的依赖注入、
事务性测试方法执行等。@ExtendWith(SpringExtension.class)
此外,由于 JUnit Jupiter 中丰富的扩展 API,Spring 提供了 除了 Spring 对 JUnit 4 支持的功能集之外,还具有以下功能。 测试NG:
-
测试构造函数、测试方法和测试生命周期回调的依赖关系注入 方法。有关更多详细信息,请参阅使用
SpringExtension
进行依赖注入。 -
对条件的强大支持 基于 SpEL 表达式、环境变量、系统属性、 等等。有关更多详细信息和示例,请参阅 Spring JUnit Jupiter 测试注解的文档和 Spring JUnit Jupiter 测试注解中的文档。
@EnabledIf
@DisabledIf
-
自定义组合的注解,结合了来自 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...
}
}
由于您还可以在 JUnit 5
中使用注解作为元注解,因此 Spring 提供了 和 组合注解来简化
测试和 JUnit Jupiter 的配置。@SpringJUnitConfig
@SpringJUnitWebConfig
ApplicationContext
以下示例用于减少配置量
在前面的示例中使用:@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...
}
}
同样,以下示例用于创建用于
JUnit Jupiter 的示例:@SpringJUnitWebConfig
WebApplicationContext
// 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...
}
}
有关更多详细信息,请参阅
Spring JUnit Jupiter 测试注解的文档和 Spring JUnit Jupiter
测试注解中的文档。@SpringJUnitConfig
@SpringJUnitWebConfig
依赖注入SpringExtension
SpringExtension
实现了来自 JUnit Jupiter 的 ParameterResolver
扩展 API,它允许 Spring 为测试提供依赖注入
构造函数、测试方法和测试生命周期回调方法。
具体来说,可以将测试中的依赖项注入到测试构造函数和方法中,这些构造函数和方法用
、 、 、 、
等进行注释。SpringExtension
ApplicationContext
@BeforeAll
@AfterAll
@BeforeEach
@AfterEach
@Test
@RepeatedTest
@ParameterizedTest
构造函数注入
如果 JUnit Jupiter
测试类的构造函数中的特定参数属于类型(或其子类型),或者用 、 或 进行注释或元注释,则
Spring 会注入该特定参数的值
参数替换为测试的相应 bean 或值。ApplicationContext
@Autowired
@Qualifier
@Value
ApplicationContext
如果出现以下情况,Spring 也可以配置为自动连接测试类构造函数的所有参数 构造函数被认为是可自动连接的。构造函数被认为是 如果满足以下条件之一(按优先顺序),则可自动接线。
-
构造函数用 批注。
@Autowired
-
@TestConstructor
在属性设置为 的测试类上存在或元存在。autowireMode
ALL
-
默认测试构造函数 autowire 模式已更改为 。
ALL
有关使用以及如何更改全局测试构造函数自动连线模式的详细信息,请参阅@TestConstructor
。@TestConstructor
如果测试类的构造函数被认为是可自动连接的,则 Spring
负责解析构造函数中所有参数的参数。
因此,没有其他在 JUnit Jupiter 注册的人可以解析
此类构造函数的参数。ParameterResolver |
测试类的构造函数注入不得与
JUnit 结合使用
Jupiter 的支持 if 用于关闭
测试之前或之后的测试方法。 原因是指示
JUnit Jupiter 缓存测试
测试方法调用之间的实例。因此,测试实例将保留
对最初从具有
随后关闭。由于测试类的构造函数只会被调用
一旦出现这种情况,依赖注入将不会再次发生,后续测试
将与关闭的 bean 交互,这可能会导致错误。 与“测试前”或“测试后”模式一起使用
结合使用,必须从 Spring 配置依赖关系
通过现场或入孵机注入提供,以便它们可以在测试之间重新注入
方法调用。 |
在下面的示例中,Spring
将 bean 从 loaded from 注入到构造函数中。OrderService
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
}
请注意,此功能允许测试依赖项是不可变的,因此是不可变的。final
如果属性是 to(参见 @TestConstructor
),我们可以省略上一个示例中构造函数的声明,从而产生以下结果。spring.test.constructor.autowire.mode
all
@Autowired
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
方法注入
如果 JUnit Jupiter
测试方法或测试生命周期回调方法中的参数为
type(或其子类型)或用 、 或 、 或 进行注释或元注释 Spring 注入该特定
参数替换为测试的
.ApplicationContext
@Autowired
@Qualifier
@Value
ApplicationContext
在以下示例中,Spring 将
from 加载的 from 注入到 test 方法中:OrderService
ApplicationContext
TestConfig.class
deleteOrder()
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
由于 JUnit Jupiter
中支持的健壮性,您还可以
将多个依赖项注入到单个方法中,不仅来自 Spring,还来自
来自 JUnit Jupiter 本身或其他第三方扩展。ParameterResolver
以下示例显示了如何让
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
}
}
请注意,使用 from
JUnit Jupiter 可以让测试方法获得访问权限
到 .@RepeatedTest
RepetitionInfo
@Nested
测试类配置
自 Spring Framework 5.0
以来,Spring TestContext Framework 支持在
JUnit Jupiter 中的测试类上使用与测试相关的注释;然而,直到春天
Framework 5.3 类级测试配置注释不是从
像来自超类一样封闭类。@Nested
Spring Framework 5.3
引入了对继承测试类的一流支持
配置,并且此类配置将由
违约。要从默认模式更改为模式,您可以注释
带有 的单个测试类。显式声明将应用于带注释的测试类以及
它的任何子类和嵌套类。因此,您可以对顶级测试类进行注释
替换为 ,这将适用于其所有嵌套测试类
递 归。INHERIT
OVERRIDE
@Nested
@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
@NestedTestConfiguration
@NestedTestConfiguration
为了允许开发团队将默认值更改为
- 例如,
为了与 Spring Framework 5.0 到 5.2 兼容 – 可以更改默认模式
全局通过 JVM 系统属性或根目录中的文件
类路径。请参阅“更改
默认封闭配置继承模式“的注释。OVERRIDE
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");
}
}
}
TestNG 支持类
该软件包提供以下支持
基于 TestNG 的测试用例的类:org.springframework.test.context.testng
-
AbstractTestNGSpringContextTests
-
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
是一个抽象基测试类,它集成了
Spring TestContext 框架,在
TestNG 环境。扩展时,可以访问可用于执行显式执行的实例变量
Bean 查找或测试整个上下文的状态。ApplicationContext
AbstractTestNGSpringContextTests
protected
applicationContext
AbstractTransactionalTestNGSpringContextTests
是一个抽象的事务扩展,它为
JDBC 增加了一些便利功能
访问。此类需要在 中定义一个 Bean 和一个 Bean。当你
extend ,您可以访问一个实例变量,该变量可用于运行 SQL 语句以查询
数据库。您可以使用此类查询来确认之前和之后的数据库状态
运行与数据库相关的应用程序代码,Spring 确保此类查询在
与应用程序代码相同的事务的范围。当与
一个 ORM 工具,一定要避免误报。
如JDBC测试支持中所述,还提供了方便的方法
通过使用上述 .
此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractTestNGSpringContextTests
javax.sql.DataSource
PlatformTransactionManager
ApplicationContext
AbstractTransactionalTestNGSpringContextTests
protected
jdbcTemplate
AbstractTransactionalTestNGSpringContextTests
JdbcTestUtils
jdbcTemplate
AbstractTransactionalTestNGSpringContextTests
executeSqlScript(..)
DataSource
这些类是扩展的便利。如果你不想要你的测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类通过使用 、 等 和
使用 .查看源代码
的示例,说明如何检测测试类。@ContextConfiguration @TestExecutionListeners TestContextManager AbstractTestNGSpringContextTests
|
3.6. WebTestClient
WebTestClient
是专为测试服务器应用程序而设计的
HTTP 客户端。它包裹
Spring 的 WebClient 并使用它来执行请求
但公开了一个用于验证响应的测试外观。 可用于
执行端到端 HTTP 测试。它还可用于测试 Spring MVC 和 Spring WebFlux
没有正在运行的服务器的应用程序,通过模拟服务器请求和响应对象。WebTestClient
Kotlin
用户:请参阅与 .WebTestClient
|
3.6.1. 设置
要设置一个,您需要选择要绑定到的服务器设置。这可以是一个
的几个模拟服务器设置选项或与实时服务器的连接。WebTestClient
绑定到控制器
此设置允许您通过模拟请求和响应对象测试特定控制器, 没有正在运行的服务器。
对于 WebFlux 应用程序,使用以下命令加载与 WebFlux Java 配置等效的基础架构,注册给定的 控制器,并创建一个 WebHandler 链来处理请求:
WebTestClient client =
WebTestClient.bindToController(new TestController()).build();
对于 Spring MVC,使用以下委托给 StandaloneMockMvcBuilder 来加载与 WebMvc Java 配置等效的基础结构, 注册给定的控制器,并创建一个 MockMvc 实例来处理请求:
WebTestClient client =
MockMvcWebTestClient.bindToController(new TestController()).build();
绑定到ApplicationContext
此设置允许您使用 Spring MVC 或 Spring WebFlux 加载 Spring 配置 基础架构和控制器声明,并使用它来通过模拟请求处理请求 和响应对象,没有正在运行的服务器。
对于 WebFlux,使用以下命令,其中
Spring 被传递给 WebHttpHandlerBuilder 以创建要处理的 WebHandler 链
请求:ApplicationContext
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {
WebTestClient client;
@BeforeEach
void setUp(ApplicationContext context) { (2)
client = WebTestClient.bindToApplicationContext(context).build(); (3)
}
}
1 | 指定要加载的配置 |
2 | 注入配置 |
3 | 创建WebTestClient |
对于 Spring MVC,使用以下命令将
Spring 传递给 MockMvcBuilders.webAppContextSetup
以创建要处理的 MockMvc 实例
请求:ApplicationContext
@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 |
绑定到路由器功能
此设置允许你通过以下方式测试功能终结点 模拟请求和响应对象,没有正在运行的服务器。
对于 WebFlux,使用以下命令将委托给
创建服务器设置以处理请求:RouterFunctions.toWebHandler
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
对于 Spring MVC,目前没有测试 WebMvc 功能端点的选项。
3.6.2. 编写测试
WebTestClient
提供与
WebClient 相同的 API,直到使用
来执行请求。有关如何执行以下操作的示例,请参阅 WebClient 文档
准备包含任何内容(包括表单数据、多部分数据等)的请求。exchange()
在调用 之后,与 和 发散
而是继续执行工作流来验证响应。exchange()
WebTestClient
WebClient
要断言响应状态和标头,请使用以下命令:
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON);
如果您希望所有期望都能得到实现,即使其中一个失败了,您可以
使用而不是多个链接调用。此功能是
类似于 AssertJ 中的软断言支持和
JUnit 木星。expectAll(..)
expect*(..)
assertAll()
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()
:解码为 JSON 内容或空正文。byte[]
并对生成的更高级别的对象执行断言:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).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)...
});
或者,您可以退出工作流并获取:EntityExchangeResult
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
当您需要使用泛型解码为目标类型时,请查找重载方法
接受
ParameterizedTypeReference
而不是 .Class<T> |
暂无内容
如果响应不应包含内容,则可以按如下方式断言:
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
如果要忽略响应内容,则释放不带响应内容的内容 任何断言:
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
JSON 内容
您可以在没有目标类型的情况下对原始数据库执行断言
内容,而不是通过更高级别的对象。expectBody()
要使用 JSONAssert 验证完整的 JSON 内容,请执行以下操作:
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");
流式响应
要测试潜在的无限流(如
或),首先验证响应状态和标头,然后
获得:"text/event-stream"
"application/x-ndjson"
FluxExchangeResult
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult(MyEvent.class);
现在,你已准备好使用来自以下位置的响应流:StepVerifier
reactor-test
Flux<Event> 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();
然后切换到 MockMvc 服务器响应断言:
MockMvcWebTestClient.resultActionsFor(result)
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"));
3.7. 模拟Mvc
Spring MVC 测试框架(也称为 MockMvc)为测试 Spring 提供支持 MVC 应用程序。它执行完整的 Spring MVC 请求处理,但通过模拟请求和 响应对象,而不是正在运行的服务器。
MockMvc 可以单独用于执行请求和验证响应。它也可以是
通过
WebTestClient 使用,其中 MockMvc 作为服务器插入以处理
请求。优点是可以选择与更高级别的工作
对象而不是原始数据,以及切换到完整的端到端 HTTP 的能力
针对实时服务器进行测试,并使用相同的测试 API。WebTestClient
3.7.1. 概述
你可以通过实例化一个控制器,注入它来为Spring
MVC编写普通的单元测试。
并调用其方法。但是,此类测试不会验证请求
映射、数据绑定、消息转换、类型转换、验证和 NOR
它们是否涉及任何支持、或方法。@InitBinder
@ModelAttribute
@ExceptionHandler
Spring MVC 测试框架,也称为
,旨在提供更完整的
在没有正在运行的服务器的情况下测试 Spring MVC 控制器。它通过调用
并从模块中传递 Servlet API 的“模拟”实现,该模块复制了完整的
Spring MVC 请求处理,而无需
正在运行的服务器。MockMvc
DispatcherServlet
spring-test
MockMvc 是一个服务器端测试框架,可让您验证大多数功能 使用轻量级和有针对性的测试的 Spring MVC 应用程序。您可以在以下位置使用它 它自己来执行请求和验证响应,或者您也可以通过 插入了 MockMvc 的 WebTestClient API 作为服务器来处理请求 跟。
静态导入
直接使用 MockMvc 执行请求时,需要静态导入:
-
MockMvcBuilders.*
-
MockMvcRequestBuilders.*
-
MockMvcResultMatchers.*
-
MockMvcResultHandlers.*
记住这一点的一种简单方法是搜索
.如果使用 Eclipse,请确保也
在 Eclipse 首选项中将上述内容添加为 “favorite static
members”。MockMvc*
通过 WebTestClient 使用 MockMvc 时,不需要静态导入。
提供流畅的 API,无需静态导入。WebTestClient
设置选择
MockMvc 可以通过以下两种方式之一进行设置。一种是直接指向您的控制器 想要测试和编程配置 Spring MVC 基础结构。二是 指向包含 Spring MVC 和控制器基础结构的 Spring 配置。
要设置 MockMvc 以测试特定控制器,请使用以下命令:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
或者,您也可以在通过委托给同一构建器的 WebTestClient 进行测试时使用此设置 如上图所示。
要通过 Spring 配置设置 MockMvc,请使用以下命令:
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
或者,您也可以在通过委托给同一构建器的 WebTestClient 进行测试时使用此设置 如上图所示。
您应该使用哪个设置选项?
加载实际的 Spring MVC
配置,从而产生更多
完成集成测试。由于 TestContext 框架缓存了加载的 Spring
配置,它有助于保持测试快速运行,即使您在
测试套件。此外,您可以通过 Spring 将模拟服务注入到控制器中
配置,以继续专注于测试 Web 层。以下示例声明
使用 Mockito 的模拟服务:webAppContextSetup
<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();
}
// ...
}
另一方面,它更接近于单元测试。它测试一个
一次控制器。您可以手动为控制器注入模拟依赖项,以及
它不涉及加载 Spring 配置。此类测试更侧重于风格
并更容易查看正在测试的控制器,是否有任何特定的 Spring
需要 MVC 配置才能工作,依此类推。这也是一个非常
编写临时测试以验证特定行为或调试问题的便捷方法。standaloneSetup
standaloneSetup
与大多数“集成与单元测试”的争论一样,没有对错之分
答。但是,使用 确实意味着需要进行额外的测试以验证您的 Spring MVC 配置。
或者,您可以使用 编写所有测试,以便始终
针对实际的 Spring MVC 配置进行测试。standaloneSetup
webAppContextSetup
webAppContextSetup
设置功能
无论您使用哪种 MockMvc
构建器,所有实现都提供
一些常见且非常有用的功能。例如,您可以声明一个标头
所有请求,并期望状态为 200 以及所有请求中的标头
响应,如下:MockMvcBuilder
Accept
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();
此外,第三方框架(和应用程序)可以预先打包设置
指令,例如 .Spring Framework 就有一个这样的
内置实现,有助于跨请求保存和重用 HTTP 会话。
您可以按如下方式使用它:MockMvcConfigurer
// static import of SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Use mockMvc to perform requests...
有关所有 MockMvc 构建器功能的列表,请参见 ConfigurableMockMvcBuilder
的
javadoc,或使用 IDE 浏览可用选项。
执行请求
本部分介绍如何单独使用
MockMvc 来执行请求和验证响应。
如果通过 MockMvc 使用 MockMvc,请参阅编写测试的相应部分。WebTestClient
执行使用任何 HTTP 方法的请求,如以下示例所示:
// static import of MockMvcRequestBuilders.*
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
您还可以执行内部使用的文件上传请求,以便不实际解析多部分
请求。相反,您必须将其设置为类似于以下示例:MockMultipartHttpServletRequest
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
可以在 URI 模板样式中指定查询参数,如以下示例所示:
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
您还可以添加表示查询或表单的 Servlet 请求参数 参数,如以下示例所示:
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
如果应用程序代码依赖于
Servlet 请求参数,并且不检查查询
字符串显式(最常见的情况),使用哪个选项并不重要。
但请记住,随 URI 模板提供的查询参数是解码的
而通过该方法提供的请求参数预计已经
被解码。param(…)
在大多数情况下,最好将上下文路径和
Servlet 路径排除在
请求 URI。如果必须使用完整的请求 URI 进行测试,请确保相应地设置 and,以便请求映射正常工作,如以下示例所示
显示:contextPath
servletPath
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
在前面的示例中,为每个执行的请求设置
and 会很麻烦。相反,您可以设置默认请求
属性,如以下示例所示:contextPath
servletPath
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
上述属性会影响通过实例执行的每个请求。
如果在给定请求中还指定了相同的属性,则它将覆盖默认值
价值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为
必须在每个请求中指定它们。MockMvc
定义期望
您可以通过在
执行请求,如以下示例所示。一旦一个期望落空,
不会断言其他期望。andExpect(..)
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(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 的具体方面,例如哪个控制器方法处理了 请求,是否引发和处理异常,模型的内容是什么, 选择了什么视图,添加了哪些闪存属性,等等。他们还让你 检查 Servlet 特定方面,例如请求和会话属性。
以下测试断言绑定或验证失败:
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
很多时候,在编写测试时,转储执行的结果很有用
请求。您可以按如下方式执行此操作,其中是静态导入:print()
MockMvcResultHandlers
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
只要请求处理不会导致未处理的异常,该方法
将所有可用的结果数据打印到 。还有一种方法和
该方法的另外两个变体,一个接受 和
一个接受 .例如,调用打印结果
data 到 ,同时调用将结果数据打印到自定义
作家。如果要记录结果数据而不是打印结果数据,可以调用该方法,该方法将结果数据记录为日志记录类别下的单个消息。print()
System.out
log()
print()
OutputStream
Writer
print(System.err)
System.err
print(myWriter)
log()
DEBUG
org.springframework.test.web.servlet.result
在某些情况下,您可能希望直接访问结果并验证以下内容
无法以其他方式进行验证。毕竟,这可以通过附加来实现
其他期望,如以下示例所示:.andReturn()
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
如果所有测试都重复相同的期望,则可以在以下情况下设置一次共同期望
生成实例,如以下示例所示:MockMvc
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
请注意,共同的期望总是被应用,如果没有,就不能被覆盖
创建单独的实例。MockMvc
当 JSON 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 JsonPath 表达式生成的链接,如以下示例所示:
mockMvc.perform(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"));
异步请求
本部分介绍如何单独使用
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 | 验证最终响应 |
流式响应
测试流式响应(如服务器发送的事件)的最佳方式是通过
WebTestClient,它可以用作连接到实例的测试客户端
在没有正在运行的服务器的情况下在 Spring MVC
控制器上执行测试。例如:MockMvc
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
WebTestClient
还可以连接到实时服务器并执行完整的端到端集成
测试。Spring Boot 也支持此功能,您可以在其中测试正在运行的服务器。
筛选注册
设置实例时,可以注册一个或多个
Servlet 实例,如以下示例所示:MockMvc
Filter
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
已注册的过滤器通过 from
调用,并且
最后一个过滤器委托给 .MockFilterChain
spring-test
DispatcherServlet
MockMvc 与端到端测试
MockMVc 基于模块中的
Servlet API 模拟实现构建,不依赖于正在运行的容器。因此,有
与完整的端到端集成测试相比,存在一些差异
客户端和正在运行的实时服务器。spring-test
考虑这个问题的最简单方法是从空白开始。
无论你向它添加什么,请求都会变成什么。可能会让你大吃一惊的事情
是默认情况下没有上下文路径;没有cookie;无转发,
错误或异步调度;因此,没有实际的 JSP 渲染。相反
“转发”和“重定向”URL 保存在 和
满怀期待地断言。MockHttpServletRequest
jsessionid
MockHttpServletResponse
这意味着,如果使用
JSP,那么可以验证请求所针对的 JSP 页面
已转发,但未呈现 HTML。换言之,不会调用 JSP。注意
但是,所有其他不依赖于转发的渲染技术,例如
Thymeleaf 和 Freemarker 按预期将 HTML 呈现到响应正文。事实也是如此
用于通过方法呈现 JSON、XML 和其他格式。@ResponseBody
或者,您可以考虑从
带有 的 Spring Boot。请参阅 Spring Boot 参考指南。@SpringBootTest
每种方法都有优点和缺点。Spring
MVC Test 中提供的选项包括
从经典单元测试到完全集成测试,规模上的不同停止。成为
当然,Spring MVC Test 中没有一个选项属于经典单元的范畴
测试,但他们离它更近一些。例如,您可以隔离 Web 图层
通过将模拟服务注入控制器,在这种情况下,您正在测试 Web
层仅通过但具有实际的 Spring 配置,因为您
可能会独立于其上层测试数据访问层。此外,您还可以使用
独立设置,一次专注于一个控制器,并手动提供
使其工作所需的配置。DispatcherServlet
使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的 测试是服务器端的,因此如果出现异常,您可以检查使用了哪个处理程序 使用 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"));
如果我们想测试允许我们创建消息的表单视图,该怎么办?例如 假设我们的窗体类似于以下代码片段:
<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());
该测试有一些明显的缺点。如果我们更新控制器以使用参数而不是
,即使 HTML 表单,我们的表单测试也会继续通过
与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,因为
遵循:message
text
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"));
这将降低我们的测试错误通过的风险,但仍存在一些风险 问题:
-
如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂: 字段类型正确?这些字段是否已启用?等等。
-
另一个问题是,我们正在做我们预期的两倍的工作。我们必须首先 验证视图,然后使用刚才验证的相同参数提交视图。 理想情况下,这可以一次完成。
-
最后,我们仍然无法解释一些事情。例如,如果表单有 我们也想测试 JavaScript 验证吗?
总体问题是,测试网页不涉及单个交互。 相反,它是用户如何与网页交互以及该网页如何交互的组合 页面与其他资源交互。例如,表单视图的结果用作 用户用于创建消息的输入。此外,我们的表单视图可能会 使用影响页面行为的其他资源,例如 JavaScript 验证。
集成测试来拯救?
为了解决前面提到的问题,我们可以执行端到端集成测试, 但这有一些缺点。考虑测试允许我们分页浏览 消息。我们可能需要以下测试:
-
我们的页面是否向用户显示通知,以指示没有结果 当消息为空时可用?
-
我们的页面是否正确显示一条消息?
-
我们的页面是否正确支持分页?
要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致一些额外的挑战:
-
确保正确的消息在数据库中可能很乏味。(考虑外键 约束。
-
测试可能会变得很慢,因为每个测试都需要确保数据库位于 正确的状态。
-
由于我们的数据库需要处于特定状态,因此我们不能并行运行测试。
-
对自动生成的 ID、时间戳等项目执行断言可以 很难。
这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以减少端到端集成测试的数量 重构我们的详细测试,以使用运行速度更快、更可靠的模拟服务, 并且没有副作用。然后,我们可以实现少量真正的端到端 集成测试,用于验证简单的工作流程,以确保一切协同工作 适当地。
HtmlUnit 集成选项
当您想要将 MockMvc 与 HtmlUnit 集成时,您有多种选择:
-
MockMvc 和 HtmlUnit:如果 想要使用原始 HtmlUnit 库。
-
MockMvc 和 WebDriver:使用此选项可以 简化集成和端到端测试之间的开发并重用代码。
-
MockMvc 和 Geb:如果需要,请使用此选项 使用 Groovy 进行测试、简化开发,并在集成和 端到端测试。
MockMvc 和 HtmlUnit
本节介绍如何集成 MockMvc 和 HtmlUnit。如果需要,请使用此选项 以使用原始 HtmlUnit 库。
MockMvc 和 HtmlUnit 设置
首先,确保已包含对
的测试依赖关系。为了将 HtmlUnit 与 Apache HttpComponents 一起使用
4.5+,您需要使用 HtmlUnit 2.18 或更高版本。net.sourceforge.htmlunit:htmlunit
我们可以轻松地创建一个与
MockMvc 集成的 HtmlUnit,如下所示:WebClient
MockMvcWebClientBuilder
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
这是使用 .对于高级用法,
请参阅高级 MockMvcWebClientBuilder 。MockMvcWebClientBuilder
|
这确保了作为服务器引用的任何
URL 都定向到我们的实例,而无需真正的 HTTP 连接。任何其他 URL 是
像往常一样使用网络连接请求。这使我们能够轻松测试
CDN。localhost
MockMvc
MockMvc 和 HtmlUnit 用法
现在我们可以像往常一样使用 HtmlUnit,但不需要部署我们的 应用程序复制到 Servlet 容器。例如,我们可以请求视图创建一个 消息包含以下内容:
HtmlPage 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();
最后,我们可以验证是否成功创建了新消息。以下 断言使用 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!");
前面的代码在许多方面改进了我们的 MockMvc 测试。 首先,我们不再需要显式验证我们的表单,然后创建一个请求 看起来像表格。取而代之的是,我们要求表格,填写并提交,从而 显著降低开销。
另一个重要因素是 HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们还可以测试 JavaScript 在我们页面中的行为。
请参阅 HtmlUnit 文档 有关使用 HtmlUnit 的其他信息。
高深MockMvcWebClientBuilder
在到目前为止的示例中,我们以最简单的方式使用
可能,通过构建一个基于为我们加载的
Spring TestContext
框架。以下示例中重复了此方法:MockMvcWebClientBuilder
WebClient
WebApplicationContext
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
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();
}
作为替代方案,我们可以通过单独配置实例并将其提供给
来执行完全相同的设置,如下所示: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();
这更冗长,但是,通过构建一个实例,我们有
MockMvc 的全部功能触手可及。WebClient
MockMvc
有关创建实例的其他信息,请参阅设置选项。MockMvc |
MockMvc 和 WebDriver
在前面的章节中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API。在本节中,我们在 Selenium WebDriver 中使用了额外的抽象,使事情变得更加容易。
为什么选择 WebDriver 和 MockMvc?
我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver?这 Selenium WebDriver 提供了一个非常优雅的 API,让我们可以轻松组织代码。自 为了更好地展示它是如何工作的,我们在本节中探讨了一个示例。
尽管是 Selenium 的一部分,但 WebDriver 没有 需要 Selenium 服务器来运行测试。 |
假设我们需要确保正确创建消息。测试包括发现 HTML 表单输入元素,填写它们,并进行各种断言。
这种方法会产生许多单独的测试,因为我们希望测试错误条件 也。例如,我们希望确保如果我们只填写 形式。如果我们填写整个表单,则应显示新创建的消息 之后。
如果其中一个字段被命名为“summary”,我们可能会有类似于 在我们的测试中,以下在多个地方重复:
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
那么,如果我们将 更改为
会发生什么?这样做会迫使我们更新所有
我们的测试以纳入此更改。这违反了 DRY 原则,因此我们应该
理想情况下,将此代码提取到其自己的方法中,如下所示:id
smmry
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);
}
这样做可以确保在更改 UI 时不必更新所有测试。
我们甚至可以更进一步,将这个逻辑放在一个
表示我们当前所处的位置,如以下示例所示:Object
HtmlPage
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());
}
}
以前,这种模式称为页面对象模式。虽然我们 当然可以用 HtmlUnit 做到这一点,WebDriver 提供了一些工具,我们在 以下各节使此模式更易于实现。
MockMvc 和 WebDriver 设置
要将 Selenium
WebDriver 与 Spring MVC 测试框架一起使用,请确保您的项目
包括对
的测试依赖关系。org.seleniumhq.selenium:selenium-htmlunit-driver
我们可以使用以下示例轻松创建与
MockMvc 集成的 Selenium WebDriver,如以下示例所示:MockMvcHtmlUnitDriverBuilder
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
这是使用 .对于更高级
用法,请参阅高级 MockMvcHtmlUnitDriverBuilder 。MockMvcHtmlUnitDriverBuilder
|
前面的示例确保引用为服务器的任何
URL 都是
定向到我们的实例,而无需真正的 HTTP 连接。任何其他
像往常一样,使用网络连接请求 URL。这让我们可以轻松测试
CDN 的使用。localhost
MockMvc
MockMvc 和 WebDriver 用法
现在,我们可以像往常一样使用 WebDriver,但不需要部署我们的 应用程序复制到 Servlet 容器。例如,我们可以请求视图创建一个 消息包含以下内容:
CreateMessagePage page = CreateMessagePage.to(driver);
然后,我们可以填写表单并提交以创建消息,如下所示:
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
这通过利用页面对象模式改进了
HtmlUnit 测试的设计。正如我们在为什么选择 WebDriver 和 MockMvc?中提到的,我们可以使用
Page 对象模式
使用 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 页面的每个部分,我们都有一个成员变量
感兴趣。这些是类型。WebDriver 的 PageFactory
允许我们删除
通过自动解析 HtmlUnit 版本中的大量代码
每。PageFactory#initElements(WebDriver,Class<T>)
方法通过使用字段名称并查找它来自动解析每个字段
由 HTML 页面中元素的 或。WebElement CreateMessagePage WebElement WebElement id name
|
3 | 我们可以使用
@FindBy
注解来覆盖默认的查找行为。我们的示例展示了如何使用注释通过选择器
(input[type=submit]) 查找提交按钮。@FindBy css
|
最后,我们可以验证是否成功创建了新消息。以下 断言使用 AssertJ 断言库:
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).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;
}
然后,我们可以在断言中使用丰富的域对象。
最后,我们不能忘记在测试完成后关闭实例,
如下:WebDriver
@AfterEach
void destroy() {
if (driver != null) {
driver.close();
}
}
有关使用 WebDriver 的其他信息,请参阅 Selenium WebDriver 文档。
高深MockMvcHtmlUnitDriverBuilder
在到目前为止的示例中,我们以最简单的方式使用
可能,通过构建一个基于为我们加载的
Spring TestContext 框架。此处重复此方法,如下所示:MockMvcHtmlUnitDriverBuilder
WebDriver
WebApplicationContext
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
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();
}
作为替代方案,我们可以通过单独配置实例并将其提供给
来执行完全相同的设置,如下所示: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();
这更冗长,但是,通过构建一个实例,我们有
MockMvc 的全部功能触手可及。WebDriver
MockMvc
有关创建实例的其他信息,请参阅设置选项。MockMvc |
MockMvc 和 Geb
在上一节中,我们了解了如何将 MockMvc 与 WebDriver 一起使用。在本节中,我们 使用 Geb 使我们的测试更加 Groovy-er。
为什么选择 Geb 和 MockMvc?
Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 Web驱动程序。然而,Geb 通过处理一些 样板代码。
MockMvc 和 Geb 设置
我们可以轻松地使用使用
MockMvc 的 Selenium WebDriver 初始化 Geb,因为
遵循:Browser
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
这是使用 .对于更高级
用法,请参阅高级 MockMvcHtmlUnitDriverBuilder 。MockMvcHtmlUnitDriverBuilder
|
这确保了任何引用服务器的
URL 都定向到我们的实例,而无需真正的 HTTP 连接。任何其他 URL 是
通过正常使用网络连接请求。这使我们能够轻松测试
CDN。localhost
MockMvc
MockMvc 和 Geb 用法
现在,我们可以像往常一样使用 Geb,但无需将应用程序部署到 一个 Servlet 容器。例如,我们可以请求视图使用 以后:
to CreateMessagePage
然后,我们可以填写表单并提交以创建消息,如下所示:
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
未找到的任何无法识别的方法调用或属性访问或引用都是 转发到当前页面对象。这删除了很多样板代码 直接使用 WebDriver 时需要。
与直接使用 WebDriver
一样,这通过使用 Page 对象改进了 HtmlUnit 测试的设计
模式。如前所述,我们可以将 Page 对象模式与 HtmlUnit 和
WebDriver,但使用 Geb 会更容易。考虑我们新的基于 Groovy 的实现:CreateMessagePage
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() }
}
}
我们的延伸
.我们不�一一��经评,但是,在
摘要,它包含我们所有页面的通用功能。我们定义了一个 URL,其中
可以找到此页面。这允许我们导航到该页面,如下所示:CreateMessagePage
Page
Page
to CreateMessagePage
我们还有一个闭包,用于确定我们是否在指定的页面。它应该
如果我们在正确的页面上,请返回。这就是为什么我们可以断言我们在
正确的页面,如下所示:at
true
then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用断言,以便我们可以确定哪里出了问题 如果我们在错误的页面上。 |
接下来,我们创建一个闭包,指定
页。我们可以使用 jQuery 式的导航器
API 来选择我们感兴趣的内容。content
最后,我们可以验证是否成功创建了新消息,如下所示:
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();
在前面的示例中,(客户端 REST 的中心类
tests) 配置一个自定义的
根据预期断言实际请求并返回“存根”响应。在这个
情况下,我们期望请求并希望返回包含内容的 200 响应。我们可以将其他预期请求和存根响应定义为
需要。当我们定义预期的请求和存根响应时,可以是
像往常一样在客户端代码中使用。在测试结束时,可以
用于验证是否满足所有期望。MockRestServiceServer
RestTemplate
ClientHttpRequestFactory
/greeting
text/plain
RestTemplate
mockServer.verify()
默认情况下,请求应按声明预期的顺序排列。你
可以在构建服务器时设置选项,在这种情况下,所有
检查期望(按顺序)以找到给定请求的匹配项。这意味着
允许以任何顺序提出请求。以下示例使用:ignoreExpectOrder
ignoreExpectOrder
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();
请注意,when 未设置(默认值),因此请求
应按声明顺序排列,则该顺序仅适用于任何
预期的请求。例如,如果“/something”应为两次,后跟
“/somewhere”三次,那么在有
对“/somewhere”的请求,但是,除了后面的“/something”和“/somewhere”之外,
请求可以随时提出。ignoreExpectOrder
作为上述所有方法的替代方法,客户端测试支持还提供了一个实现,您可以将其配置为
将其绑定到实例。这允许使用实际的服务器端处理请求
逻辑,但不运行服务器。以下示例演示如何执行此操作:ClientHttpRequestFactory
RestTemplate
MockMvc
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// Test code that uses the above RestTemplate ...
3.8.1. 静态导入
与服务器端测试一样,用于客户端测试的
Fluent API 需要一些静态
进口。通过搜索很容易找到这些。Eclipse 用户应将 和 添加为
“Java→编辑器”下的“Eclipse 首选项中的”收藏静态成员“→ Content
协助→收藏夹。这允许在键入
静态方法名称。其他 IDE(如 IntelliJ)可能不需要任何额外的
配置。检查是否支持对静态成员的代码完成。MockRest*
MockRestRequestMatchers.*
MockRestResponseCreators.*
3.8.2. 客户端REST测试的更多例子
Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。
4. 更多资源
有关测试的详细信息,请参阅以下资源:
-
JUnit:“一个程序员友好的 Java 测试框架”。 由 Spring Framework 在其测试套件中使用,并在 Spring TestContext Framework 中受支持。
-
TestNG:受 JUnit 启发的测试框架,增加了支持 用于测试组、数据驱动测试、分布式测试和其他功能。支持 在 Spring TestContext 框架中
-
AssertJ: “Fluent 断言 for Java”, 包括对 Java 8 lambda、流和其他功能的支持。
-
Mock Objects:维基百科中的文章。
-
MockObjects.com:专门用于模拟对象的网站,一个 在测试驱动开发中改进代码设计的技术。
-
Mockito:基于 Test Spy 模式的 Java mock 库。由 Spring Framework 使用 在其测试套件中。
-
EasyMock:Java 库“为 接口(和通过类扩展的对象),通过使用 Java 的代理机制。
-
JMock:支持 Java 代码测试驱动开发的库 替换为模拟对象。
-
DbUnit:JUnit 扩展(也可用于 Ant 和 Maven),用于 针对数据库驱动的项目,除其他外,将您的数据库放入 测试运行之间的已知状态。
-
Testcontainers:支持 JUnit 的 Java 库 测试,提供通用数据库的轻量级、一次性实例,Selenium Web 浏览器,或任何其他可以在 Docker 容器中运行的东西。
-
Grinder:Java 负载测试框架。
-
SpringMockK:支持 Spring Boot 使用 MockK 而不是 Mockito 用 Kotlin 编写的集成测试。