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