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