测试
2. 单元测试
依赖项注入应该使您的代码对容器的依赖程度低于它
与传统的 Java EE 开发。组成应用程序的 POJO 应该
可在 JUnit 或 TestNG 测试中进行测试,使用运算符实例化对象,无需 Spring 或任何其他容器。您可以使用 mock 对象 (与其他有价值的测试技术结合使用) 来单独测试您的代码。
如果遵循 Spring 的体系结构建议,则生成的 Clean Layering
代码库的组件化有助于更轻松地进行单元测试。例如
您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,
无需在运行单元测试时访问持久性数据。new
真正的单元测试通常运行得非常快,因为没有运行时基础设施 建立。强调真正的单元测试作为开发方法的一部分可以促进 您的工作效率。你可能不需要测试章节的这一部分来帮助你编写 对基于 IoC 的应用程序进行有效的单元测试。对于某些单元测试场景, 但是,Spring 框架提供了 mock 对象和测试支持类,这些 在本章中进行了介绍。
2.1. 模拟对象
Spring 包含许多专门用于 mock 的包:
2.1.1. 环境
该包包含 和 abstractions 的模拟实现(参见 Bean 定义配置文件和 PropertySource
抽象)。 ,并且对开发很有用
容器外测试依赖于特定于环境的属性的代码。org.springframework.mock.env
Environment
PropertySource
MockEnvironment
MockPropertySource
2.1.2. JNDI
该软件包包含 JNDI 的部分实现
SPI,可用于为测试套件或独立设置简单的 JNDI 环境
应用。例如,如果 JDBC 实例绑定到同一个 JNDI
名称,因此您可以重用这两个应用程序代码
以及测试场景中的配置。org.springframework.mock.jndi
DataSource
包中的模拟 JNDI 支持是
从 Spring Framework 5.2 开始正式弃用,取而代之的是 Third 的完整解决方案
Simple-JNDI 等各方。org.springframework.mock.jndi |
2.1.3. Servlet API
该软件包包含一套全面的 Servlet API
模拟对象,这些对象对测试 Web 上下文、控制器和过滤器很有用。这些
mock 对象的目标是与 Spring 的 Web MVC 框架一起使用,并且通常更多
比动态 mock 对象(如 EasyMock)更方便使用
或替代 Servlet API 模拟对象(例如 MockObjects)。org.springframework.mock.web
从 Spring Framework 5.0 开始,中的 mock 对象是
基于 Servlet 4.0 API。org.springframework.mock.web |
Spring MVC 测试框架构建在模拟 Servlet API 对象之上,以提供 Spring MVC 的集成测试框架。参见 Spring MVC 测试框架。
2.1.4. Spring Web 响应式
该包包含 mock 实现
的 Web 应用程序。该软件包包含一个 mock,该
依赖于这些 mock 请求和响应对象。org.springframework.mock.http.server.reactive
ServerHttpRequest
ServerHttpResponse
org.springframework.mock.web.server
ServerWebExchange
Both 和 extend from the same abstract
基类作为特定于服务器的实现,并与它们共享行为。为
示例,模拟请求一旦创建就不可变,但你可以使用
from 创建修改后的实例。MockServerHttpRequest
MockServerHttpResponse
mutate()
ServerHttpRequest
为了使 mock 响应正确实现写入协定并返回
写入完成句柄(即 ),默认情况下,它使用 with ,该句柄缓冲数据并使其可用于测试中的断言。
应用程序可以设置自定义写入函数(例如,测试无限流)。Mono<Void>
Flux
cache().then()
WebTestClient 基于模拟请求和响应构建,以提供对 在没有 HTTP 服务器的情况下测试 WebFlux 应用程序。客户端还可用于 使用正在运行的服务器进行端到端测试。
2.2. 单元测试支持类
Spring 包含许多可以帮助进行单元测试的类。他们分为两个 类别:
2.2.1. 通用测试工具
该软件包包含几个通用实用程序
用于单元和集成测试。org.springframework.test.util
ReflectionTestUtils
是基于反射的实用程序方法的集合。您可以使用
这些方法在需要更改常量值的测试场景中,将
非字段,调用非 setter 方法,或者在测试应用程序代码的用例时调用非配置或生命周期回调方法
例如:public
public
public
-
纵容或字段的 ORM 框架(例如 JPA 和 Hibernate) 访问域实体中属性的 setter 方法。
private
protected
public
-
Spring 对 Comments(例如 、 、 和 )、 为 OR 字段提供依赖关系注入、setter 方法、 和配置方法。
@Autowired
@Inject
@Resource
private
protected
-
使用 annotations,例如 和 for lifecycle callback 方法。
@PostConstruct
@PreDestroy
AopTestUtils
是
与 AOP 相关的实用程序方法。您可以使用这些方法获取对
隐藏在一个或多个 Spring 代理后面的底层目标对象。例如,如果你
通过使用 EasyMock 或 Mockito 等库将 bean 配置为动态模拟,
并且 mock 包装在 Spring 代理中,则可能需要直接访问底层
mock 来配置对它的期望并执行验证。对于 Spring 的核心 AOP
实用工具,请参阅 AopUtils
和 AopProxyUtils
。
2.2.2. Spring MVC 测试工具
该包包含 ModelAndViewAssert
,您可以
可以与 JUnit、TestNG 或任何其他测试框架结合使用进行单元测试
处理 Spring MVC 对象。org.springframework.test.web
ModelAndView
对 Spring MVC 控制器进行单元测试 要将 Spring MVC 类作为 POJO 进行单元测试,请使用 Spring 的 Servlet API mock 中的 、 和 等的组合。为了对您的
Spring MVC 和 REST 类与 Spring MVC 的配置结合使用,请改用 Spring MVC 测试框架。Controller ModelAndViewAssert MockHttpServletRequest MockHttpSession Controller WebApplicationContext |
3. 集成测试
本节(本章其余大部分内容)涵盖了 Spring 的集成测试 应用。它包括以下主题:
3.1. 概述
能够在不需要的情况下执行一些集成测试非常重要 部署到您的应用程序服务器或连接到其他企业基础设施。 这样做可以让你测试以下内容:
-
Spring IoC 容器上下文的正确连接。
-
使用 JDBC 或 ORM 工具进行数据访问。这可能包括正确性 SQL 语句、Hibernate 查询、JPA 实体映射等。
Spring Framework 为模块中的集成测试提供了一流的支持。实际 JAR 文件的名称可能包括发行版本
也可能是长格式,具体取决于您获得的位置
it from (有关说明,请参阅 Dependency Management 部分)。此库包括
包含用于与 Spring 容器进行集成测试的有价值的类。此测试
不依赖于应用程序服务器或其他部署环境。此类测试是
运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者
依赖于部署到应用程序服务器的远程测试。spring-test
org.springframework.test
org.springframework.test
单元和集成测试支持以 Comments 驱动的 Spring TestContext Framework 的形式提供。TestContext 框架是 与使用的实际测试框架无关,允许对测试进行检测 在各种环境中,包括 JUnit、TestNG 等。
3.2. 集成测试的目标
Spring 的集成测试支持具有以下主要目标:
-
管理测试之间的 Spring IoC 容器缓存。
-
提供适合集成测试的事务管理。
-
提供特定于 Spring 的基类来提供帮助 编写集成测试的开发人员。
接下来的几节描述了每个目标,并提供了指向实现和 配置详细信息。
3.2.1. 上下文管理和缓存
Spring TestContext 框架提供 Spring 实例和实例的一致加载以及缓存
的那些背景。支持缓存已加载的上下文非常重要,因为
启动时间可能会成为一个问题 — 不是因为 Spring 本身的开销,而是
因为 Spring 容器实例化的对象需要时间来实例化。为
例如,具有 50 到 100 个 Hibernate 映射文件的项目可能需要 10 到 20 秒才能完成
加载映射文件,并在每个测试中运行每个测试之前产生该成本
夹具会导致整体测试运行速度变慢,从而降低开发人员的工作效率。ApplicationContext
WebApplicationContext
测试类通常声明 XML 或 Groovy 的资源位置数组
配置元数据(通常在 Classpath 中)或组件类数组
用于配置应用程序。这些位置或类与 或
类似于 或 生产环境的其他配置文件中指定的
部署。web.xml
默认情况下,加载后,配置的将用于每个测试。
因此,每个测试套件和后续测试执行仅产生一次设置成本
要快得多。在这种情况下,术语“测试套件”是指所有测试都在同一环境中运行
JVM — 例如,所有测试都从给定项目的 Ant、Maven 或 Gradle 构建运行
或模块。在不太可能的情况下,测试会破坏应用程序上下文,并且需要
重新加载(例如,通过修改 Bean 定义或应用程序的状态
对象),TestContext 框架可以配置为重新加载配置,并且
在执行下一个测试之前重新构建应用程序上下文。ApplicationContext
3.2.2. 测试 Fixture 的依赖注入
当 TestContext 框架加载您的应用程序上下文时,它可以选择:
使用 Dependency Injection 配置测试类的实例。这提供了一个
通过使用
应用程序上下文。这里的一个很大好处是您可以重用应用程序上下文
在各种测试场景中(例如,用于配置 Spring 托管对象
图、事务代理、实例等),从而避免了
需要为单个测试用例复制复杂的测试夹具设置。DataSource
例如,考虑一个场景,我们有一个类 ()
实现域实体的数据访问逻辑。我们想写
测试以下领域的集成测试:HibernateTitleRepository
Title
-
Spring 配置:基本上,与 bean 的配置相关的一切都正确且存在吗?
HibernateTitleRepository
-
Hibernate 映射文件配置:所有内容是否都正确映射,并且 正确的延迟加载设置是否正确?
-
的逻辑 : 执行该类的已配置实例 按预期执行?
HibernateTitleRepository
请参阅使用 TestContext 框架对测试 fixture 进行依赖注入。
3.2.3. 事务管理
在访问真实数据库的测试中,一个常见的问题是它们对 持久化存储。即使您使用开发数据库,对状态的更改也可能 影响未来的测试。此外,还有许多操作 — 例如插入或修改持久性 data — 不能在事务之外执行(或验证)。
TestContext 框架解决了这个问题。默认情况下,框架会创建 和
为每个测试回滚一个事务。您可以编写可以假定存在的代码
交易的如果您在测试中调用事务代理对象,则它们的行为
正确地,根据他们配置的事务语义。此外,如果测试
method 在事务中运行时删除所选表的内容
managed,则事务默认回滚,并且数据库返回到
它在执行测试之前的状态。事务性支持由
使用在测试的应用程序上下文中定义的 bean。PlatformTransactionManager
如果您希望事务提交(不常见,但当您希望
particular test 来填充或修改数据库),你可以告诉 TestContext
框架,通过使用 @Commit
注解来使事务提交而不是回滚。
请参阅使用 TestContext 框架进行事务管理。
3.2.4. 集成测试的支持类
Spring TestContext 框架提供了几个支持类,这些类
简化集成测试的编写。这些基本测试类提供了定义明确的
钩子以及方便的实例变量和方法,
这样,您就可以访问:abstract
-
用于执行显式 bean 查找或测试 整个环境。
ApplicationContext
-
A ,用于执行 SQL 语句来查询数据库。你可以使用这样的 查询以确认数据库相关 应用程序代码,并且 Spring 确保此类查询在相同的 transaction 作为应用程序代码。与 ORM 工具结合使用时,请确保 以避免误报。
JdbcTemplate
此外,您可能希望使用 特定于您的项目的实例变量和方法。
请参阅 TestContext 框架的支持类。
3.3. JDBC 测试支持
该软件包包含 ,它是一个
旨在简化标准数据库的 JDBC 相关实用程序函数的集合
测试场景。具体来说,提供以下静态实用程序
方法。org.springframework.test.jdbc
JdbcTestUtils
JdbcTestUtils
-
countRowsInTable(..)
:计算给定表中的行数。 -
countRowsInTableWhere(..)
:使用 provided 子句。WHERE
-
deleteFromTables(..)
:从指定表中删除所有行。 -
deleteFromTableWhere(..)
:使用 provided 子句从给定表中删除行。WHERE
-
dropTables(..)
:删除指定的表。
该模块支持配置和启动嵌入式
database,您可以在与数据库交互的集成测试中使用。
有关详细信息,请参阅嵌入式数据库
支持和测试数据访问
具有嵌入式数据库的 Logic 。 |
3.4. 注解
本节介绍在测试 Spring 应用程序时可以使用的注释。 它包括以下主题:
3.4.1. Spring 测试注解
Spring Framework 提供了以下一组特定于 Spring 的 Comments,您可以 可以在单元测试和集成测试中与 TestContext 框架结合使用。 有关更多信息,请参阅相应的 javadoc,包括 default 属性 值、属性别名和其他详细信息。
Spring 的 testing 注释包括以下内容:
@BootstrapWith
@BootstrapWith
是一个类级注解,可用于配置 Spring
TestContext Framework 是引导的。具体来说,你可以使用
指定自定义 .有关更多详细信息,请参阅有关引导 TestContext 框架的部分。@BootstrapWith
TestContextBootstrapper
@ContextConfiguration
@ContextConfiguration
定义用于确定如何
加载并配置 for integration tests。具体来说,声明应用程序上下文资源或
组件来加载上下文。ApplicationContext
@ContextConfiguration
locations
classes
资源位置通常是 XML 配置文件或位于
classpath 的 API,而组件类通常是 Class。然而
资源位置还可以引用文件系统和组件中的文件和脚本
类可以是类、类等。有关更多详细信息,请参阅 Component Classes 。@Configuration
@Component
@Service
以下示例显示了引用 XML 的注释
文件:@ContextConfiguration
@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 | 引用类。 |
作为替代方案或除了声明资源位置或组件类之外,
你可以使用 to 声明类。
以下示例显示了这种情况:@ContextConfiguration
ApplicationContextInitializer
@ContextConfiguration(initializers = CustomContextIntializer.class) (1)
class ContextInitializerTests {
// class body...
}
@ContextConfiguration(initializers = [CustomContextIntializer::class]) (1)
class ContextInitializerTests {
// class body...
}
1 | 声明初始值设定项类。 |
您可以选择使用 将策略声明为
井。但请注意,您通常不需要显式配置 loader,
由于默认加载程序支持 Resource 或
元件。@ContextConfiguration
ContextLoader
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 提供对继承资源位置的支持,或者
配置类以及由 superclasses 声明的上下文初始化器。 |
有关详细信息,请参阅 Context Management 和 javadocs
详。@ContextConfiguration
@WebAppConfiguration
@WebAppConfiguration
是一个类级注释,您可以使用它来声明为集成测试加载的应该是 .
仅 on 测试类的存在可确保为测试加载 a,使用 Web 应用程序根目录路径的默认值 (即
resource base path) 的 Sample。资源基路径在后台用于创建 ,该路径用作测试的 。ApplicationContext
WebApplicationContext
@WebAppConfiguration
WebApplicationContext
"file:src/main/webapp"
MockServletContext
ServletContext
WebApplicationContext
以下示例演示如何使用注释:@WebAppConfiguration
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
1 | 注释。@WebAppConfiguration |
要覆盖默认值,您可以使用
implicit 属性。和 resource 前缀都是
支持。如果未提供资源前缀,则假定该路径为文件系统
资源。下面的示例展示了如何指定 Classpath 资源:value
classpath:
file:
@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
javadoc。@WebAppConfiguration
@ContextConfiguration
@ContextHierarchy
@ContextHierarchy
是类级注释,用于定义集成测试的实例层次结构。 应该是
使用一个或多个实例的列表声明,每个实例
定义上下文层次结构中的级别。以下示例演示了在单个测试类 ( 也可以使用
在 Test 类层次结构中):ApplicationContext
@ContextHierarchy
@ContextConfiguration
@ContextHierarchy
@ContextHierarchy
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}
@ContextHierarchy(
ContextConfiguration("/parent-config.xml"),
ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [AppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
// class body...
}
如果需要合并或覆盖给定上下文级别的配置
层次结构中,您必须通过提供
相同的值添加到每个对应的
level 的 LEVEL 中。请参阅上下文层次结构和@ContextHierarchy
javadoc
以获取更多示例。name
@ContextConfiguration
@ActiveProfiles
@ActiveProfiles
是用于声明哪个 bean 的类级 Comments
定义配置文件在加载 for an 时应处于活动状态
集成测试。ApplicationContext
以下示例指示配置文件应处于活动状态:dev
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | 指示配置文件应处于活动状态。dev |
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | 指示配置文件应处于活动状态。dev |
以下示例指示 和 配置文件都应该
保持活跃:dev
integration
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | 指示 和 用户档案 应处于活动状态。dev integration |
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | 指示 和 用户档案 应处于活动状态。dev integration |
@ActiveProfiles 支持继承活动的 Bean 定义配置文件
默认情况下由 Superclasses 声明。您还可以解析活动的 Bean 定义配置文件
通过实现自定义 ActiveProfilesResolver 并使用 的属性注册它,以编程方式进行编程。resolver @ActiveProfiles |
请参阅使用环境配置文件的上下文配置和@ActiveProfiles
javadoc 了解
示例和更多详细信息。
@TestPropertySource
@TestPropertySource
是类级注解,可用于配置
属性文件和内联属性的位置,这些属性文件和内联属性将添加到 for an for an
集成测试。PropertySources
Environment
ApplicationContext
下面的示例演示了如何从 Classpath 声明属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 从 Classpath 的根目录中获取属性。test.properties |
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | 从 Classpath 的根目录中获取属性。test.properties |
下面的示例演示如何声明内联属性:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
// class body...
}
1 | Declare 和 properties.timezone port |
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
// class body...
}
1 | Declare 和 properties.timezone port |
@DynamicPropertySource
@DynamicPropertySource
是一个方法级注释,可用于注册要添加到 for
一个 loaded 用于集成测试。动态属性很有用
当您不知道 properties 的值时 — 例如,如果 properties
由外部资源管理,例如由 Testcontainers 项目管理的容器。PropertySources
Environment
ApplicationContext
下面的示例演示如何注册动态属性:
@ContextConfiguration
class MyIntegrationTests {
static MyExternalServer server = // ...
@DynamicPropertySource (1)
static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
registry.add("server.port", server::getPort); (3)
}
// tests ...
}
1 | 使用 .static @DynamicPropertySource |
2 | 接受 a 作为参数。DynamicPropertyRegistry |
3 | 注册要从服务器延迟检索的动态属性。server.port |
@ContextConfiguration
class MyIntegrationTests {
companion object {
@JvmStatic
val server: MyExternalServer = // ...
@DynamicPropertySource (1)
@JvmStatic
fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
registry.add("server.port", server::getPort) (3)
}
}
// tests ...
}
1 | 使用 .static @DynamicPropertySource |
2 | 接受 a 作为参数。DynamicPropertyRegistry |
3 | 注册要从服务器延迟检索的动态属性。server.port |
@DirtiesContext
@DirtiesContext
表示底层 Spring 已
在执行测试期间被弄脏(即,测试在
某种方式 — 例如,通过更改单例 bean 的状态),并且应该是
闭。当应用程序上下文被标记为 dirty 时,它会从测试中删除
framework 的 cache 和 closed。因此,底层的 Spring 容器是
为需要具有相同配置的上下文的任何后续测试重新构建
元数据。ApplicationContext
您可以用作
相同的类或类层次结构。在这种情况下,将标记
作为 dirty 在任何此类 Comments 方法之前或之后,以及当前
test 类,具体取决于配置的 和 .@DirtiesContext
ApplicationContext
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 在当前测试类之前弄脏上下文。 -
在当前测试类之后,当在 class mode 设置为 (即默认 class mode) 的类上声明时。
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 在当前测试方法之后弄脏上下文。
如果您在测试中使用其上下文配置为上下文的一部分
hierarchy 中,您可以使用标志来控制
上下文缓存将被清除。默认情况下,使用穷举算法来清除
context cache,不仅包括当前级别,还包括所有其他上下文
共享当前测试通用的上级上下文的层次结构。驻留在公共祖先的子层次结构中的所有实例
context 将从上下文缓存中删除并关闭。如果穷举算法是
overkill 对于特定用例,您可以指定更简单的 current level 算法
如下例所示。@DirtiesContext
@ContextHierarchy
hierarchyMode
ApplicationContext
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}
class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
void test() {
// some logic that results in the child context being dirtied
}
}
1 | 使用 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 算法。 |
有关 and 算法的更多详细信息,请参阅 DirtiesContext.HierarchyMode
javadoc。EXHAUSTIVE
CURRENT_LEVEL
@TestExecutionListeners
@TestExecutionListeners
定义类级元数据,用于配置应向 .通常与 结合使用。TestExecutionListener
TestContextManager
@TestExecutionListeners
@ContextConfiguration
以下示例说明如何注册两个 implementations:TestExecutionListener
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 注册两个 implementations。TestExecutionListener |
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 注册两个 implementations。TestExecutionListener |
默认情况下,支持继承的侦听器。有关示例和更多详细信息,请参阅 javadoc。@TestExecutionListeners
@Commit
@Commit
指示事务测试方法的事务应为
在测试方法完成后提交。您可以用作直接
replacement for 以更明确地传达代码的意图。
类似于 ,也可以声明为类级或方法级
注解。@Commit
@Rollback(false)
@Rollback
@Commit
以下示例演示如何使用注释:@Commit
@Commit (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 将测试结果提交到数据库。 |
@Commit (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 将测试结果提交到数据库。 |
@Rollback
@Rollback
指示事务测试方法的事务是否应为
在测试方法完成后回滚。如果 ,则事务已滚动
返回。否则,将提交事务(另请参见 @Commit
)。Spring 中集成测试的回滚
TestContext Framework 默认为 即使未明确声明。true
true
@Rollback
当声明为类级 Comments 时,定义默认回滚
Test Class 层次结构中所有测试方法的语义。当声明为
方法级注释,定义特定测试的回滚语义
方法,可能会覆盖类级或语义。@Rollback
@Rollback
@Rollback
@Commit
以下示例导致测试方法的结果不回滚(即 result 提交到数据库):
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 不要回滚结果。 |
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 不要回滚结果。 |
@BeforeTransaction
@BeforeTransaction
表示带注解的方法应该在
事务,对于已配置为在
transaction 的 Comments。 方法
不需要,并且可以在基于 Java 8 的 interface default 上声明
方法。void
@Transactional
@BeforeTransaction
public
以下示例演示如何使用注释:@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
指示带注解的方法应在
transaction 的
transaction 的 Comments。 方法
不需要,并且可以在基于 Java 8 的 interface default 上声明
方法。void
@Transactional
@AfterTransaction
public
@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 脚本的元数据
配置了 Annotation。以下示例演示如何使用它:@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
用于注释测试类或测试方法,以配置
方法级声明与类级声明合并。如果未在测试类或测试方法上声明,则合并模式
将默认使用。使用 mode,方法级声明将
有效地覆盖类级声明。@Sql
@Sql
@SqlMergeMode
OVERRIDE
OVERRIDE
@Sql
@Sql
请注意,方法级声明会覆盖类级声明。@SqlMergeMode
下面的示例演示如何在类级别使用。@SqlMergeMode
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将 merge mode 设置为 for all test methods in the class.@Sql 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 | 将 merge mode 设置为 for all test methods in the class.@Sql MERGE |
以下示例演示如何在方法级别使用。@SqlMergeMode
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) (1)
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 将 merge mode (合并模式) 设置为 for a specific test method(针对特定测试方法)。@Sql 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 | 将 merge mode (合并模式) 设置为 for a specific test method(针对特定测试方法)。@Sql MERGE |
@SqlGroup
@SqlGroup
是聚合多个注释的容器注释。您可以
使用 natively 来声明多个嵌套的注解,或者你可以使用它
结合 Java 8 对可重复注释的支持,其中 can
在同一个类或方法上多次声明,隐式生成此容器
注解。以下示例说明如何声明 SQL 组:@Sql
@SqlGroup
@Sql
@Sql
@Test
@SqlGroup({ (1)
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
1 | 声明一组 SQL 脚本。 |
@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 框架中,你可以使用 and with
在 .
但是,这些生命周期注释在实际测试类中的使用受到限制。 如果测试类中的方法使用 进行批注,则该方法运行
在底层测试框架的任何 before 方法(例如,方法
用 JUnit Jupiter 的 注释),这适用于
test 类。另一方面,如果测试类中的方法用 注释 ,则该方法永远不会运行。因此,在 test 类中,我们建议
您可以使用来自底层测试框架的测试生命周期回调,而不是 和 。 |
3.4.3. Spring JUnit 4 测试注解
@IfProfileValue
@IfProfileValue
表示为特定测试启用了带注释的测试
环境。如果 configured 返回
,则启用测试。否则,测试将被禁用,并且实际上
忽视。ProfileValueSource
value
name
您可以在类级别和/或方法级别应用。
的类级用法优先于任何
方法。具体来说,如果测试是
在类级别和方法级别都启用。缺少 表示隐式启用测试。这与 JUnit 4 的 annotation 的语义类似,不同之处在于 的存在总是会禁用测试。@IfProfileValue
@IfProfileValue
@IfProfileValue
@Ignore
@Ignore
以下示例显示了一个带有注释的测试:@IfProfileValue
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
1 | 仅当 Java 供应商为“Oracle Corporation”时,才运行此测试。 |
@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”时,才运行此测试。 |
或者,您也可以使用列表(带有语义)进行配置,以实现对 JUnit 4 环境中测试组的类似 TestNG 的支持。
请考虑以下示例:@IfProfileValue
values
OR
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
1 | 对单元测试和集成测试运行此测试。 |
@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
是指定类型的类级注释
of 检索通过注释配置的配置文件值时使用。如果未为
test。以下示例说明如何
用: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 的 Comments 与 JUnit 4 的支持具有不同的语义。具体来说,由于 JUnit 4 处理测试执行超时的方式
(即,通过在单独的 中执行 test 方法),如果测试时间过长,则抢先使测试失败。另一方面是 Spring 的
hand 不会抢先使测试失败,而是等待测试完成
在失败之前。@Timed
@Test(timeout=…)
Thread
@Test(timeout=…)
@Timed
@Repeat
@Repeat
表示必须重复运行带注释的测试方法。的
在注释中指定要运行测试方法的时间。
要重复的执行范围包括测试方法本身的执行,如
以及测试夹具的任何设置或拆除。当与 SpringMethodRule
一起使用时,范围还包括
通过实现准备测试实例。这
以下示例显示了如何使用注释:TestExecutionListener
@Repeat
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
// ...
}
1 | 重复此测试十次。 |
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
// ...
}
1 | 重复此测试十次。 |
3.4.4. Spring JUnit Jupiter 测试注解
仅当与 SpringExtension
和 JUnit Jupiter 结合使用时,才支持以下 Comments
(即 JUnit 5 中的编程模型):
@SpringJUnitConfig
@SpringJUnitConfig
是一个组合的注释,它将 from JUnit Jupiter 与 from 组合在一起
Spring TestContext 框架。它可以在类级别用作 drop-in
替换 .对于配置选项,唯一的
和 之间的区别是那个组件
类可以使用 中的属性进行声明。@ExtendWith(SpringExtension.class)
@ContextConfiguration
@ContextConfiguration
@ContextConfiguration
@SpringJUnitConfig
value
@SpringJUnitConfig
以下示例演示如何使用注释指定
configuration 类:@SpringJUnitConfig
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置类。 |
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置类。 |
以下示例演示如何使用注释指定
配置文件的位置:@SpringJUnitConfig
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置文件的位置。 |
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | 指定配置文件的位置。 |
请参阅 上下文管理 以及 javadoc 以了解@SpringJUnitConfig
和更多详细信息。@ContextConfiguration
@SpringJUnitWebConfig
@SpringJUnitWebConfig
是一个组合的 Comments,它结合了 JUnit Jupiter 和 Spring TestContext Framework。您可以在课堂上使用它
level 作为 和 的直接替换。
关于配置选项,和 之间的唯一区别是可以使用 中的属性声明组件类。此外,您只能使用 中的属性来覆盖 中的属性。@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebAppConfiguration
@ContextConfiguration
@WebAppConfiguration
@ContextConfiguration
@SpringJUnitWebConfig
value
@SpringJUnitWebConfig
value
@WebAppConfiguration
resourcePath
@SpringJUnitWebConfig
以下示例演示如何使用注释指定
一个配置类:@SpringJUnitWebConfig
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | 指定配置类。 |
@SpringJUnitWebConfig(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 | 指定配置文件的位置。 |
有关更多详细信息,请参阅上下文管理以及 @SpringJUnitWebConfig
、@ContextConfiguration
和 @WebAppConfiguration
的 javadoc。
@TestConstructor
@TestConstructor
是一个类型级注解,用于配置参数
的测试类构造函数是从测试的 .ApplicationContext
如果 在测试类中不存在或元存在,则默认测试
构造函数 autowire 模式。有关如何更改的详细信息,请参阅下面的提示
默认模式。但是请注意,在
constructor 优先于两者和 default 模式。@TestConstructor
@Autowired
@TestConstructor
更改默认测试构造函数 autowire 模式
可以通过将 JVM 系统属性设置为 来更改默认测试构造函数 autowire 模式。或者,
default 模式可以通过 如果未设置该属性,则 test 类
构造函数不会自动装配。 |
从 Spring Framework 5.2 开始,仅支持结合使用
与 for use with JUnit Jupiter.请注意,该
通常会自动为您注册 – 例如,当使用 AND 等 OR 或各种与测试相关的注释时
Spring Boot 测试。@TestConstructor SpringExtension SpringExtension @SpringJUnitConfig @SpringJUnitWebConfig |
@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 {}
@DisabledIf
@DisabledIf
用于指示带注释的 JUnit Jupiter 测试类或测试
方法已禁用,如果提供的计算结果为 ,则不应运行 。具体而言,如果表达式的计算结果为或等于
to (忽略大小写),则禁用测试。在类级别应用时,所有
该类中的 test 方法也会自动禁用。expression
true
Boolean.TRUE
String
true
表达式可以是以下任何一项:
-
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 {}
3.4.5. 测试的元注解支持
您可以使用大多数与测试相关的注释作为元注释来创建自定义组合 注释并减少测试套件中的配置重复。
您可以将以下各项作为 TestContext 框架的元注释。
-
@BootstrapWith
-
@ContextConfiguration
-
@ContextHierarchy
-
@ActiveProfiles
-
@TestPropertySource
-
@DirtiesContext
-
@WebAppConfiguration
-
@TestExecutionListeners
-
@Transactional
-
@BeforeTransaction
-
@AfterTransaction
-
@Commit
-
@Rollback
-
@Sql
-
@SqlConfig
-
@SqlMergeMode
-
@SqlGroup
-
@Repeat
(仅在 JUnit 4 上受支持) -
@Timed
(仅在 JUnit 4 上受支持) -
@IfProfileValue
(仅在 JUnit 4 上受支持) -
@ProfileValueSourceConfiguration
(仅在 JUnit 4 上受支持) -
@SpringJUnitConfig
(仅在 JUnit Jupiter 上受支持) -
@SpringJUnitWebConfig
(仅在 JUnit Jupiter 上受支持) -
@TestConstructor
(仅在 JUnit Jupiter 上受支持) -
@EnabledIf
(仅在 JUnit Jupiter 上受支持) -
@DisabledIf
(仅在 JUnit Jupiter 上受支持)
请考虑以下示例:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
如果我们发现我们在基于 JUnit 4 的 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 { }
然后我们可以使用自定义 annotation 来简化
配置基于 JUnit 4 的各个测试类,如下所示:@TransactionalDevTestConfig
@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 { }
然后我们可以使用自定义 annotation 来简化
配置基于 JUnit Jupiter 的各个测试类,如下所示:@TransactionalDevTestConfig
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
由于 JUnit Jupiter 支持使用 、
和其他作为元注释的注释,您还可以在
测试方法级别。例如,如果我们希望创建一个组合
和 JUnit Jupiter 的注解与 Spring 的注解一起,我们可以创建一个注解,因为
遵循:@Test
@RepeatedTest
ParameterizedTest
@Test
@Tag
@Transactional
@TransactionalIntegrationTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }
然后我们可以使用自定义 annotation 来简化
配置基于 JUnit Jupiter 的各个测试方法,如下所示:@TransactionalIntegrationTest
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
@TransactionalIntegrationTest
fun saveOrder() { }
@TransactionalIntegrationTest
fun deleteOrder() { }
有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
3.5. Spring TestContext 框架
Spring TestContext 框架(位于包中)提供了通用的、注解驱动的单元和集成测试支持,即
与正在使用的测试框架无关。TestContext 框架还放置了一个很棒的
重视约定而不是配置,具有合理的默认值
可以通过基于注释的配置进行覆盖。org.springframework.test.context
除了通用测试基础设施外,TestContext 框架还提供
显式支持 JUnit 4、JUnit Jupiter(又名 JUnit 5)和 TestNG。对于 JUnit 4 和
TestNG 中,Spring 提供了支持类。此外,Spring 还提供了一个自定义的
JUnit 和自定义 JUnit 4 的自定义 JUnit 以及 JUnit 的自定义
Jupiter 允许您编写所谓的 POJO 测试类。POJO 测试类不是
扩展特定类层次结构(如支持类)时需要。abstract
Runner
Rules
Extension
abstract
以下部分概述了 TestContext 框架的内部结构。 如果您只对使用框架感兴趣,而对扩展它不感兴趣 使用您自己的自定义侦听器或自定义加载器,请随时直接转到 配置(上下文管理、依赖关系注入、事务 management)、support classes 和 annotation support 部分。
3.5.1. 键抽象
框架的核心由类、 、 和 接口组成。为每个测试类创建一个 A(例如,为了执行
JUnit Jupiter 中单个测试类中的所有测试方法)。这
反过来,管理 A 保存当前测试的上下文。还会随着测试的进行而更新 的状态
并委托给 implementations,这些 implementations 将实际的
通过提供依赖项注入、管理事务等来测试执行。A 负责为给定的测试加载
类。请参阅 javadoc 和
Spring test 套件,以获取更多信息和各种实现的示例。TestContextManager
TestContext
TestExecutionListener
SmartContextLoader
TestContextManager
TestContextManager
TestContext
TestContextManager
TestContext
TestExecutionListener
SmartContextLoader
ApplicationContext
TestContext
TestContext
封装运行测试的上下文(与
实际测试框架),并为
它负责的 test 实例。该 还委托给 a 以加载 if requested.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
定义用于响应
侦听器注册到的 。请参阅 TestExecutionListener
配置。TestContextManager
上下文加载器
ContextLoader
是一个策略接口,用于为
由 Spring TestContext 框架管理的集成测试。您应该实现而不是此接口来提供对组件类
活动 Bean 定义配置文件、测试属性源、上下文层次结构和支持。ApplicationContext
SmartContextLoader
WebApplicationContext
SmartContextLoader
是接口的扩展,它取代了
原始的最小 SPI。具体来说,可以选择
进程资源位置、组件类或上下文初始值设定项。此外,可以在
它加载的上下文。ContextLoader
ContextLoader
SmartContextLoader
SmartContextLoader
Spring 提供了以下实现:
-
DelegatingSmartContextLoader
:两个默认加载器之一,它在内部委托给 an 、 a 或 a ,具体取决于为 test 类或是否存在 default locations 或 default configuration 类。 仅当 Groovy 位于 Classpath 上时,才会启用 Groovy 支持。AnnotationConfigContextLoader
GenericXmlContextLoader
GenericGroovyXmlContextLoader
-
WebDelegatingSmartContextLoader
:两个默认加载器之一,它在内部委托 更改为 、 a 或 a ,具体取决于为 测试类或存在 default locations 或 default configuration 类。仅当 Web 位于 test 类。仅当 Groovy 位于 Classpath 上时,才会启用 Groovy 支持。AnnotationConfigWebContextLoader
GenericXmlWebContextLoader
GenericGroovyXmlWebContextLoader
ContextLoader
@WebAppConfiguration
-
AnnotationConfigContextLoader
:从零部件加载标准 类。ApplicationContext
-
AnnotationConfigWebContextLoader
:加载 from 组件 类。WebApplicationContext
-
GenericGroovyXmlContextLoader
:从资源加载标准 位置,可以是 Groovy 脚本或 XML 配置文件。ApplicationContext
-
GenericGroovyXmlWebContextLoader
:加载 from 资源 位置,可以是 Groovy 脚本或 XML 配置文件。WebApplicationContext
-
GenericXmlContextLoader
:从 XML 资源加载标准 地点。ApplicationContext
-
GenericXmlWebContextLoader
:加载 from XML 资源 地点。WebApplicationContext
-
GenericPropertiesContextLoader
:从 Java 加载标准 properties 文件。ApplicationContext
3.5.2. 引导 TestContext 框架
Spring TestContext 框架内部的默认配置是
足以满足所有常见使用案例的需求。但是,有时开发团队或
第三方框架想改变默认的 ,实现一个
custom 或 ,扩充默认的 AND 实施集,依此类推。为
这种对 TestContext 框架运行方式的低级控制, Spring 提供了一个
引导策略。ContextLoader
TestContext
ContextCache
ContextCustomizerFactory
TestExecutionListener
TestContextBootstrapper
定义用于引导 TestContext 框架的 SPI。A 用于加载当前测试的实现并构建它所管理的 。您可以为
test 类(或测试类层次结构),直接使用 ,也可以作为
meta-annotation 中。如果未使用 显式配置引导程序,则使用 或 ,具体取决于是否存在 。TestContextBootstrapper
TestContextManager
TestExecutionListener
TestContext
@BootstrapWith
@BootstrapWith
DefaultTestContextBootstrapper
WebTestContextBootstrapper
@WebAppConfiguration
由于 SPI 将来可能会发生变化(以适应
new requirements),我们强烈建议实现者不要实现此接口
直接,而是延伸或它的混凝土之一
子类。TestContextBootstrapper
AbstractTestContextBootstrapper
3.5.3. 配置TestExecutionListener
Spring 提供了以下已注册的实现
默认情况下,完全按以下顺序:TestExecutionListener
-
ServletTestExecutionListener
:为 配置 Servlet API 模拟。WebApplicationContext
-
DirtiesContextBeforeModesTestExecutionListener
:处理 “before” 模式的注释。@DirtiesContext
-
DependencyInjectionTestExecutionListener
:为测试提供依赖项注入 实例。 -
DirtiesContextTestExecutionListener
:处理 “after” 模式。@DirtiesContext
-
TransactionalTestExecutionListener
:提供事务测试执行 default 回滚语义。 -
SqlScriptsTestExecutionListener
:运行使用注释配置的 SQL 脚本。@Sql
-
EventPublishingTestExecutionListener
:将测试执行事件发布到测试的 (请参阅 测试执行事件)。ApplicationContext
注册实现TestExecutionListener
您可以为测试类及其
子类。有关详细信息和示例,请参阅 Comments Support 和 javadoc for @TestExecutionListeners
。TestExecutionListener
@TestExecutionListeners
自动发现默认实施TestExecutionListener
使用 is 注册实现
适用于在有限测试场景中使用的自定义监听器。但是,它可以
如果需要在整个测试套件中使用自定义侦听器,则会变得很麻烦。这
此问题已通过支持通过 MECHANISM 自动发现默认实现得到解决。TestExecutionListener
@TestExecutionListeners
TestExecutionListener
SpringFactoriesLoader
具体来说,该模块在 key in
其属性文件。第三方框架和开发人员
可以将自己的 implementation 贡献给 default 的列表
listeners 以相同的方式通过它们自己的属性
文件。spring-test
TestExecutionListener
org.springframework.test.context.TestExecutionListener
META-INF/spring.factories
TestExecutionListener
META-INF/spring.factories
订购实现TestExecutionListener
当 TestContext 框架发现默认实现时
通过上述机制,实例化的监听器使用
Spring 的,它遵循 Spring 的接口和注释进行排序。 以及 Spring 提供的所有默认实现都使用
适当的值。因此,第三方框架和开发人员应确保
它们的默认 implementations 以正确的 Sequences 注册
通过实现或声明 .有关核心默认实现的方法,请参阅 javadoc 以了解
值将分配给每个核心侦听器。TestExecutionListener
SpringFactoriesLoader
AnnotationAwareOrderComparator
Ordered
@Order
AbstractTestExecutionListener
TestExecutionListener
Ordered
TestExecutionListener
Ordered
@Order
getOrder()
TestExecutionListener
合并实施TestExecutionListener
如果自定义是通过 注册的,
默认侦听器未注册。在最常见的测试场景中,这有效地
强制开发人员手动声明所有默认侦听器以及任何自定义
听众。下面的清单演示了这种配置样式:TestExecutionListener
@TestExecutionListeners
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
MyCustomTestExecutionListener::class,
ServletTestExecutionListener::class,
DirtiesContextBeforeModesTestExecutionListener::class,
DependencyInjectionTestExecutionListener::class,
DirtiesContextTestExecutionListener::class,
TransactionalTestExecutionListener::class,
SqlScriptsTestExecutionListener::class
)
class MyTest {
// class body...
}
这种方法的挑战在于它要求开发人员确切地知道
默认注册哪些侦听器。此外,默认侦听器集可以
从一个版本到另一个版本的变化 — 例如,WAS
在 Spring Framework 4.1 中引入,并在 Spring Framework 4.2 中引入。此外,像 Spring 这样的第三方框架
Boot 和 Spring Security 使用上述自动发现机制注册自己的默认实现。SqlScriptsTestExecutionListener
DirtiesContextBeforeModesTestExecutionListener
TestExecutionListener
为避免必须了解并重新声明所有默认侦听器,您可以将 的属性设置为 。 指示本地声明的侦听器应与
default 侦听器。合并算法可确保从
list 中,并且生成的合并侦听器集根据语义进行排序
的,如对 TestExecutionListener
实现进行排序中所述。
如果侦听器实现 或用 注释 ,则它可以影响
位置,它与默认值合并。否则,本地声明的侦听器
在合并时附加到默认侦听器列表中。mergeMode
@TestExecutionListeners
MergeMode.MERGE_WITH_DEFAULTS
MERGE_WITH_DEFAULTS
AnnotationAwareOrderComparator
Ordered
@Order
例如,如果上一个示例中的类
将其值(例如)配置为小于 的顺序(恰好是 ),然后可以自动与
defaults 的 ,前面的示例可以
替换为以下内容:MyCustomTestExecutionListener
order
500
ServletTestExecutionListener
1000
MyCustomTestExecutionListener
ServletTestExecutionListener
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
listeners = [MyCustomTestExecutionListener::class],
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
3.5.4. 测试执行事件
Spring Framework 5.2 中引入的提供了一个
实现自定义 .组件
test 可以侦听 发布的以下事件,每个事件对应于 API 中的一个方法。EventPublishingTestExecutionListener
TestExecutionListener
ApplicationContext
EventPublishingTestExecutionListener
TestExecutionListener
-
BeforeTestClassEvent
-
PrepareTestInstanceEvent
-
BeforeTestMethodEvent
-
BeforeTestExecutionEvent
-
AfterTestExecutionEvent
-
AfterTestMethodEvent
-
AfterTestClassEvent
仅当 已加载时,才会发布这些事件。ApplicationContext |
这些事件可能由于各种原因而被使用,例如重置 mock bean 或跟踪
测试执行。使用测试执行事件而不是实现
自定义是测试执行事件可以被任何
Spring bean 在 test 中注册,这样的 bean 可能会受益
直接从依赖项注入和 .在
相反,a 不是 中的 bean。TestExecutionListener
ApplicationContext
ApplicationContext
TestExecutionListener
ApplicationContext
为了侦听测试执行事件,Spring bean 可以选择实现接口。或者,侦听器
方法可以注释并配置为侦听
上面列出的特定事件类型(请参阅基于 Comments 的事件侦听器)。
由于这种方法的流行, Spring 提供了以下专用 Comments 来简化测试执行事件侦听器的注册。
这些注释驻留在包中。org.springframework.context.ApplicationListener
@EventListener
@EventListener
org.springframework.test.context.event.annotation
-
@BeforeTestClass
-
@PrepareTestInstance
-
@BeforeTestMethod
-
@BeforeTestExecution
-
@AfterTestExecution
-
@AfterTestMethod
-
@AfterTestClass
异常处理
默认情况下,如果测试执行事件侦听器在使用
事件,该异常将传播到正在使用的底层测试框架(例如
JUnit 或 TestNG)。例如,如果 a 的消耗导致
异常,则相应的测试方法将因异常而失败。在
相反,如果异步测试执行事件侦听器引发异常,则
exception 不会传播到底层测试框架。有关更多详细信息
异步异常处理,请参阅类级 Javadoc 以获取 。BeforeTestMethodEvent
@EventListener
异步侦听器
如果您希望特定的测试执行事件侦听器异步处理事件,
您可以使用 Spring 的常规 @Async
支持。有关更多详细信息,请查阅类级 javadoc 以获取 。@EventListener
3.5.5. 上下文管理
每个都为测试实例提供上下文管理和缓存支持
它负责。测试实例不会自动获得对
配置。但是,如果测试类实现接口,则会提供对
添加到测试实例中。请注意,并实现 and,因此,
提供对 自动.TestContext
ApplicationContext
ApplicationContextAware
ApplicationContext
AbstractJUnit4SpringContextTests
AbstractTestNGSpringContextTests
ApplicationContextAware
ApplicationContext
@Autowired ApplicationContext
作为实现接口的替代方法,你可以将
测试类的应用程序上下文,通过
Field 或 setter 方法,如下例所示: Java
Kotlin
同样,如果您的测试配置为加载 ,则可以将
Web 应用程序上下文添加到测试中,如下所示: Java
Kotlin
using 的依赖注入由 提供,默认情况下已配置
(参见 Dependency Injection of Test Fixtures)。 |
使用 TestContext 框架的测试类不需要扩展任何特定的
类或实现特定接口来配置其应用程序上下文。相反
配置是通过在
类级别。如果您的测试类未显式声明应用程序上下文资源
locations 或组件类,则 configured 决定了如何加载
context 从默认位置或默认配置类。除了上下文
资源位置和组件类,也可以配置应用程序上下文
通过 Application Context Initializers 进行初始化。@ContextConfiguration
ContextLoader
以下部分解释了如何使用 Spring 的注解来
使用 XML 配置文件、Groovy 脚本、
组件类(通常是 Classes)或上下文初始值设定项。
或者,您可以实现和配置自己的自定义
高级用例。@ContextConfiguration
ApplicationContext
@Configuration
SmartContextLoader
使用 XML 资源的上下文配置
要使用 XML 配置文件加载 for your tests,请注释
使用 test 类配置属性
一个包含 XML 配置元数据的资源位置的数组。普通或
相对路径(例如,)被视为类路径资源,该
相对于定义测试类的 package。以斜杠开头的路径
被视为绝对 Classpath 位置(例如 )。一个
表示资源 URL 的路径(即前缀为 、 、 等的路径)按原样使用。ApplicationContext
@ContextConfiguration
locations
context.xml
/org/example/config.xml
classpath:
file:
http:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | 将 locations 属性设置为 XML 文件列表。 |
@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
支持通过
standard Java 属性。因此,如果您不需要声明额外的
attributes 中,您可以省略属性名称的声明,并使用速记格式声明资源位置
以下示例演示:locations
value
@ContextConfiguration
locations
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | 指定 XML 文件而不使用 the 属性。location |
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
// class body...
}
1 | 指定 XML 文件而不使用 the 属性。location |
如果从注解中同时省略 the 和 the attributes,则 TestContext 框架会尝试检测默认的
XML 资源位置。具体而言,并根据测试的名称检测默认位置
类。如果您的类名为 ,则加载
应用程序上下文。以下内容
示例展示了如何做到这一点:locations
value
@ContextConfiguration
GenericXmlContextLoader
GenericXmlWebContextLoader
com.example.MyTest
GenericXmlContextLoader
"classpath:com/example/MyTest-context.xml"
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
使用 Groovy 脚本进行上下文配置
要使用使用 Groovy Bean Definition DSL 的 Groovy 脚本加载测试,您可以注解
使用包含 Groovy 脚本资源位置的数组配置 or 属性。资源
Groovy 脚本的查找语义与 XML 配置文件中描述的相同。ApplicationContext
@ContextConfiguration
locations
value
启用 Groovy 脚本支持 支持使用 Groovy 脚本在 Spring 中加载
如果 Groovy 在 Classpath 上,则会自动启用 TestContext Framework。ApplicationContext |
下面的示例展示了如何指定 Groovy 配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
// class body...
}
@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 配置文件的位置。 |
如果从注释中省略了 and 属性,则 TestContext 框架将尝试检测默认的 Groovy 脚本。
具体来说,并根据测试类的名称检测默认位置。如果您的类名为 ,则 Groovy 上下文加载器将从 加载您的应用程序上下文。以下示例演示如何使用
默认值:locations
value
@ContextConfiguration
GenericGroovyXmlContextLoader
GenericGroovyXmlWebContextLoader
com.example.MyTest
"classpath:com/example/MyTestContext.groovy"
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | 从默认位置加载配置。 |
同时声明 XML 配置和 Groovy 脚本
您可以使用
的 or 属性。如果
配置的资源位置以 结尾,它是使用 .否则,将使用 . 下面的清单显示了如何在集成测试中将两者结合起来: Java
Kotlin
|
使用组件类的上下文配置
要使用组件类加载 for your tests(请参阅基于 Java 的容器配置),您可以对测试 进行注释
class 替换为 array 并配置属性
,其中包含对组件类的引用。以下示例显示了如何执行此操作:ApplicationContext
@ContextConfiguration
classes
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
// class body...
}
1 | 指定组件类。 |
@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” 可以指以下任何内容:
有关更多信息,请参阅 |
如果从注释中省略该属性,则
TestContext 框架尝试检测是否存在默认配置类。
具体来说,并检测 test 类中满足
配置类实现,如 @Configuration
JavaDoc 中指定。
请注意,配置类的名称是任意的。此外,测试类可以
如果需要,包含多个嵌套的 Configuration 类。在以下
例如,该类声明了一个嵌套的配置类
named 的 API 自动用于加载 for test
类:classes
@ContextConfiguration
AnnotationConfigContextLoader
AnnotationConfigWebContextLoader
static
static
OrderServiceTest
static
Config
ApplicationContext
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {
@Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
OrderService orderService;
@Test
void testOrderService() {
// test the orderService
}
}
1 | 从嵌套类加载配置信息。Config |
@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 脚本和
组件类(通常是 Classes)来配置一个。例如,如果您在
production 中,您可以决定要使用 classes 来配置
特定的 Spring Management 组件,反之亦然。@Configuration
ApplicationContext
@Configuration
此外,一些第三方框架(例如 Spring Boot)提供了一流的
支持从不同类型的资源加载
同时(例如,XML 配置文件、Groovy 脚本和类)。Spring 框架历史上不支持此功能
标准部署。因此,大多数
Spring Framework 在模块中只提供一种资源类型
对于每个测试上下文。但是,这并不意味着您不能同时使用两者。一
一般规则的例外是 and 同时支持 XML 配置文件和 Groovy
脚本。此外,第三方框架可以选择支持
声明 and through 、 and,以及
TestContext 框架中的标准测试支持,您有以下选项。ApplicationContext
@Configuration
SmartContextLoader
spring-test
GenericGroovyXmlContextLoader
GenericGroovyXmlWebContextLoader
locations
classes
@ContextConfiguration
如果要使用资源位置(例如 XML 或 Groovy)和类来配置测试,则必须选择一个作为入口点,并且该类必须
include 或 import other。例如,在 XML 或 Groovy 脚本中,您可以通过使用组件扫描或将它们定义为普通 Spring 来包含类
bean,而在类中,您可以使用它来导入 XML
配置文件或 Groovy 脚本。请注意,此行为在语义上是等效的
了解如何在生产环境中配置应用程序:在生产配置中,您
定义一组 XML 或 Groovy 资源位置或一组从中加载您的产品的类,但您仍然拥有
自由包含或导入其他类型的配置。@Configuration
@Configuration
@Configuration
@ImportResource
@Configuration
ApplicationContext
使用 Context Initializers 进行 Context Configuration
要使用上下文初始值设定项为测试配置 an,
使用数组注释测试类,并使用数组配置属性,该数组包含对实现 .然后,使用声明的上下文初始值设定项
初始化为测试加载的 。请注意,
每个声明的初始化器支持的具体类型
必须与 创建的 类型兼容 正在使用(通常为 )。此外,
调用初始值设定项的顺序取决于它们是实现 Spring 的接口还是使用 Spring 的 Comments 或标准 Comments 进行 Comments。以下示例演示如何使用初始值设定项:ApplicationContext
@ContextConfiguration
initializers
ApplicationContextInitializer
ConfigurableApplicationContext
ConfigurableApplicationContext
ApplicationContext
SmartContextLoader
GenericApplicationContext
Ordered
@Order
@Priority
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class) (1)
class MyTest {
// class body...
}
1 | 使用配置类和初始值设定项指定配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = [TestConfig::class],
initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
// class body...
}
1 | 使用配置类和初始值设定项指定配置。 |
您还可以省略 XML 配置文件、Groovy 脚本或
组件类,而是只声明类,然后负责注册 bean
在上下文中 — 例如,通过以编程方式从 XML 加载 Bean 定义
文件或配置类。以下示例显示了如何执行此操作:@ContextConfiguration
ApplicationContextInitializer
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
// class body...
}
1 | 仅使用初始值设定项指定配置。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
// class body...
}
1 | 仅使用初始值设定项指定配置。 |
上下文配置继承
@ContextConfiguration
支持布尔值和属性,这些属性表示是资源位置还是组件类和上下文
超类声明的初始化器应该被继承。两者的默认值
flags 为 .这意味着测试类继承资源位置或
组件类以及任何超类声明的上下文初始化器。
具体来说,将附加测试类的资源位置或组件类
添加到由 Superclasses 声明的资源位置或带注释的类的列表。
同样,给定测试类的初始化器将添加到初始化器集中
由 test superclasses 定义。因此,子类可以选择扩展资源
locations、Component classes 或 Context Initializers 的 Initializer 进行初始化。inheritLocations
inheritInitializers
true
如果 中的 或 属性设置为 ,则资源位置或组件类以及上下文
initializers 分别用于测试类 shadow 并有效地替换
由超类定义的配置。inheritLocations
inheritInitializers
@ContextConfiguration
false
在下一个使用 XML 资源位置的示例中,将按此顺序从 和 加载 for。
因此,中定义的 bean 可以覆盖(即替换)这些
在 中定义。下面的示例展示了一个类如何扩展
another 并使用自己的配置文件和 superclass 的配置文件:ApplicationContext
ExtendedTest
base-config.xml
extended-config.xml
extended-config.xml
base-config.xml
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 配置文件。 |
2 | 配置文件。 |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | 配置文件。 |
2 | 配置文件。 |
同样,在下一个使用组件类的示例中,for 是从 和 类加载的,因为
次序。因此,中定义的 bean 可以覆盖(即 replace)
在 中定义的 .下面的示例展示了一个类如何扩展
另一个,并使用自己的 Configuration 类和 Superclass 的 Configuration 类:ApplicationContext
ExtendedTest
BaseConfig
ExtendedConfig
ExtendedConfig
BaseConfig
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 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 类。 |
在下一个使用上下文初始值设定项的示例中,使用 和 初始化 for 。注意
但是,调用初始值设定项的顺序取决于它们是否
实现 Spring 的接口或使用 Spring 的 Comments 进行 Comments
或标准注释。以下示例显示了一个类如何
扩展另一个 Initializer 并同时使用它自己的 Initializer 和 Superclass 的 Initializer:ApplicationContext
ExtendedTest
BaseInitializer
ExtendedInitializer
Ordered
@Order
@Priority
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | 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 定义配置文件。这是通过以下方式实现的
使用注解注释测试类并提供
在加载 for the test.@ActiveProfiles
ApplicationContext
您可以与 SPI 的任何实现一起使用,但不支持旧 SPI 的实现。@ActiveProfiles SmartContextLoader @ActiveProfiles ContextLoader |
考虑两个包含 XML 配置和类的示例:@Configuration
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@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
}
}
运行时,将从 Classpath 根目录中的配置文件加载它。如果检查 ,则可以看到 Bean 依赖于 Bean。但是,未定义为顶级 Bean。相反,定义了三次:在配置文件中,在配置文件中,
和配置文件中。TransferServiceTest
ApplicationContext
app-config.xml
app-config.xml
accountRepository
dataSource
dataSource
dataSource
production
dev
default
通过使用 进行注释,我们指示 Spring
TestContext Framework 加载 ,并将活动配置文件设置为 。因此,将创建一个嵌入式数据库,并使用测试数据填充,并且
该 bean 与 development 的引用连接。
这可能是我们在集成测试中想要的。TransferServiceTest
@ActiveProfiles("dev")
ApplicationContext
{"dev"}
accountRepository
DataSource
有时,将 bean 分配给配置文件很有用。默认值内的 Bean
仅当没有专门激活其他配置文件时,才会包含配置文件。您可以使用
这是为了定义要在应用程序的默认状态中使用的 “fallback” bean。为
例如,您可以明确提供 AND 用户档案的数据源,
但是,当内存中数据源都未处于活动状态时,请将内存中数据源定义为默认值。default
dev
production
以下代码清单演示了如何实现相同的配置和
使用类而不是 XML 进行集成测试:@Configuration
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("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
:使用 获取 through dependency injection 。dataSource
@Autowired
-
StandaloneDataConfig
:为嵌入式数据库定义一个 开发人员测试。dataSource
-
JndiDataConfig
:定义从生产中的 JNDI 检索的 环境。dataSource
-
DefaultDataConfig
:为默认嵌入式数据库定义 a,如果为 配置文件处于活动状态。dataSource
与基于 XML 的配置示例一样,我们仍然使用 进行注释,但这次我们通过
使用注释。测试类本身的主体仍然存在
完全没有改变。TransferServiceTest
@ActiveProfiles("dev")
@ContextConfiguration
通常情况下,在多个测试类中使用一组配置文件
在给定项目中。因此,为了避免注释的重复声明,您可以在基类和子类上声明一次
自动从基类继承配置。在
以下示例中,声明 (以及其他注释)
已移动到抽象超类 :@ActiveProfiles
@ActiveProfiles
@ActiveProfiles
@ActiveProfiles
AbstractIntegrationTest
@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 定义配置文件,您可以实现
a custom 并使用 的属性注册它。有关更多信息,请参阅相应的 javadoc。
以下示例演示如何实现和注册 custom :ActiveProfilesResolver
resolver
@ActiveProfiles
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 源。与类上使用的注释相反,您可以在测试中声明注释
类来声明测试属性文件或内联属性的资源位置。
这些测试属性源将添加到 for the loaded for the annotated integration test 的集合中。@PropertySource
@Configuration
@TestPropertySource
PropertySources
Environment
ApplicationContext
您可以与 SPI 的任何实现一起使用,但不支持旧 SPI 的实现。 获取对合并的测试属性源值的访问权限的实现
通过 和 中的 方法。 |
声明测试属性源
您可以使用 的 or 属性配置测试属性文件。locations
value
@TestPropertySource
支持传统和基于 XML 的属性文件格式,例如 或 ."classpath:/com/example/test.properties"
"file:///path/to/file.xml"
每个 path 都解释为 Spring 。纯路径(例如 )被视为相对于包的 Classpath 资源
其中定义了测试类。以斜杠开头的路径被视为
绝对 Classpath 资源(例如:)。一条
引用 URL(例如,前缀为 、 或 的路径 )是
使用指定的资源协议加载。不允许使用资源位置通配符(例如 ):每个位置的计算结果必须恰好为 1 或 资源。Resource
"test.properties"
"/org/example/test.xml"
classpath:
file:
http:
*/.properties
.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 开始,可以用作可重复的 Comments。
这意味着您可以在单个
test 类,其中 和 from later 注解会覆盖先前注解中的 API。 此外,您可以在一个测试类上声明多个组合注释,每个注释都是
元注释,所有这些声明都将有助于您的测试属性源。 直接存在的注释始终优先于
meta-present 注解。换句话说,and from 一个直接存在的注解将覆盖 and from 一个用作
meta-annotation 中。 |
默认属性文件检测
If 声明为空注解(即,没有显式的
值)时,会尝试检测
default 属性文件。例如
如果带注释的测试类为 ,则相应的默认属性
file 为 .如果无法检测到默认值,则引发 an。@TestPropertySource
locations
properties
com.example.MyTest
classpath:com/example/MyTest.properties
IllegalStateException
优先
测试属性的优先级高于操作系统的
环境、Java 系统属性或应用程序添加的属性源
以声明方式使用 OR 以编程方式。因此,测试属性可以
用于选择性地覆盖从 System 和 Application 属性加载的属性
来源。此外,内联属性的优先级高于加载的属性
从资源位置。但请注意,通过 @DynamicPropertySource
注册的属性具有
比通过 .@PropertySource
@TestPropertySource
在下一个示例中,和 properties 以及 中定义的任何属性将覆盖 system 中定义的任何同名属性
和应用程序属性源。此外,如果文件定义了
和 属性的条目被内联的
使用 attribute 声明的属性。以下示例显示了如何操作
要在 File 和 Inline 中指定属性:timezone
port
"/test.properties"
"/test.properties"
timezone
port
properties
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
// class body...
}
@ContextConfiguration
@TestPropertySource("/test.properties",
properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
// class body...
}
继承和覆盖测试属性源
@TestPropertySource
支持布尔值和属性,这些属性表示属性文件和内联的资源位置
超类声明的属性应该被继承。这两个标志的默认值
是。这意味着测试类继承 locations 和 inlined 属性
由任何超类声明。具体来说,一个
test 类附加到 superclasses 声明的 locations 和 inlined 属性中。
因此,子类可以选择扩展 locations 和 inlined 属性。注意
稍后出现的属性会隐藏(即 override)同名的属性
出现得更早。此外,上述优先规则也适用于继承的
test 属性源。inheritLocations
inheritProperties
true
如果 中的 or 属性为
分别设置为 Test 类的 locations 或 inlined 属性
shadow 并有效地替换 superclasses 定义的配置。inheritLocations
inheritProperties
@TestPropertySource
false
在下一个示例中,仅将文件用作测试属性源来加载 for。相比之下,for 是使用 和 文件作为测试属性源位置来加载的。以下示例显示了如何定义
使用 files 在子类及其 superclass 中的属性:ApplicationContext
BaseTest
base.properties
ApplicationContext
ExtendedTest
base.properties
extended.properties
properties
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
在下一个示例中,仅使用
inlined 属性。相比之下,for 是
使用 inlined 和 属性加载。以下示例显示了如何操作
要使用 inline properties 在 subclass 及其 superclass 中定义 properties:ApplicationContext
BaseTest
key1
ApplicationContext
ExtendedTest
key1
key2
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
使用动态属性源的上下文配置
从 Spring Framework 5.2.5 开始,TestContext 框架通过 Comments 提供对动态属性的支持。此注释可用于
需要将具有动态值的属性添加到集合中的集成测试,对于已加载的
集成测试。@DynamicPropertySource
PropertySources
Environment
ApplicationContext
注释及其支持基础设施是
最初设计为允许基于 Testcontainers 的测试中的属性很容易地暴露给
Spring 集成测试。但是,此功能也可以与任何形式的
外部资源,其生命周期维护在测试的 .@DynamicPropertySource ApplicationContext |
与在类级别应用的 @TestPropertySource
注释相反,必须应用
转换为接受单个参数的方法,该参数是
用于将名称-值对添加到 .值是动态的,并通过
a 中调用的 API 调用,该 API 仅在解析属性时调用。通常,方法
引用用于提供值,如以下示例所示,该示例使用
Testcontainers 项目来管理 Spring 之外的 Redis 容器。托管 Redis 容器的 IP 地址和端口已创建
可通过 和 属性提供给测试中的组件。这些属性可以通过 Spring 的抽象访问,也可以直接注入到 Spring Management 的组件中——例如,分别为 via 和 。@DynamicPropertySource
static
DynamicPropertyRegistry
Environment
Supplier
ApplicationContext
ApplicationContext
redis.host
redis.port
Environment
@Value("${redis.host}")
@Value("${redis.port}")
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
@Container
static RedisContainer redis = new RedisContainer();
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("redis.host", redis::getContainerIpAddress);
registry.add("redis.port", redis::getMappedPort);
}
// tests ...
}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
companion object {
@Container
@JvmStatic
val redis: RedisContainer = RedisContainer()
@DynamicPropertySource
@JvmStatic
fun redisProperties(registry: DynamicPropertyRegistry) {
registry.add("redis.host", redis::getContainerIpAddress)
registry.add("redis.port", redis::getMappedPort)
}
}
// tests ...
}
加载一个WebApplicationContext
要指示 TestContext 框架加载 a 而不是
standard 中,您可以使用 .WebApplicationContext
ApplicationContext
@WebAppConfiguration
on 你的 test 类的存在指示 TestContext
框架 (TCF) 中加载的 (WAC)
集成测试。在后台,TCF 确保 为
创建并提供给测试的 WAC。默认情况下,您的基本资源路径设置为 。这被解释为路径相对
添加到 JVM 的根目录(通常是项目的路径)。如果您熟悉
目录结构中,您知道这是 WAR 根目录的默认位置。如果您需要
override this default,则可以提供注释的备用路径(例如 )。如果您愿意
从 Classpath 而不是文件系统中引用基本资源路径,您可以使用
Spring 的前缀。@WebAppConfiguration
WebApplicationContext
MockServletContext
MockServletContext
src/main/webapp
src/main/webapp
@WebAppConfiguration
@WebAppConfiguration("src/test/webapp")
classpath:
请注意, Spring 对 implementation 的 testing 支持是相当的
支持标准实现。使用 进行测试时,您可以自由地声明 XML 配置文件、Groovy 脚本、
或 类。您也可以免费使用
任何其他测试注释,例如 、 等。WebApplicationContext
ApplicationContext
WebApplicationContext
@Configuration
@ContextConfiguration
@ActiveProfiles
@TestExecutionListeners
@Sql
@Rollback
本节中的其余示例显示了
加载 .以下示例显示了 TestContext
框架对约定优于配置的支持:WebApplicationContext
@ExtendWith(SpringExtension.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
//...
}
@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 {
//...
}
如果在未指定资源的情况下对测试类进行注释
base path 时,资源路径实际上默认为 .同样地
如果在未指定 resource 、 component 或 context 的情况下进行声明,则 Spring 会尝试检测是否存在
使用约定进行配置(即,在同一个包中
作为类或静态嵌套类)。@WebAppConfiguration
file:src/main/webapp
@ContextConfiguration
locations
classes
initializers
WacTests-context.xml
WacTests
@Configuration
以下示例演示如何使用 显式声明资源基本路径,并使用 显式声明 XML 资源位置 :@WebAppConfiguration
@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 的不同语义
附注。默认情况下,资源路径是基于文件系统的,
而资源位置是基于 Classpath 的。@WebAppConfiguration
@ContextConfiguration
下面的示例显示,我们可以覆盖两者的默认资源语义 注解:
@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 框架默认启用。当针对 .
每个测试方法并创建一个 、 、 和
a 基于配置了 的基本资源路径。 也保证了 and 可以注入到测试实例中,
并且,一旦测试完成,它就会清理线程本地状态。
ServletTestExecutionListener
WebApplicationContext
RequestContextHolder
MockHttpServletRequest
MockHttpServletResponse
ServletWebRequest
@WebAppConfiguration
ServletTestExecutionListener
MockHttpServletResponse
ServletWebRequest
加载测试后,您可能会发现
需要与 Web mock 进行交互 — 例如,设置测试夹具或
在调用 Web 组件后执行断言。以下示例显示了
mock 可以自动连接到你的测试实例中。请注意,和 都缓存在测试套件中,而其他 mock 是
每个测试方法由 进行管理。WebApplicationContext
WebApplicationContext
MockServletContext
ServletTestExecutionListener
@SpringJUnitWebConfig
class WacTests {
@Autowired
WebApplicationContext wac; // cached
@Autowired
MockServletContext servletContext; // cached
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
@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 框架加载了 (或 )
对于测试,该上下文将被缓存并重新用于所有声明
同一测试套件中相同的唯一上下文配置。了解如何缓存
有效,理解 “unique” 和 “test suite” 的含义非常重要。ApplicationContext
WebApplicationContext
可以通过配置的组合来唯一标识
参数。因此,配置的独特组合
parameters 用于生成缓存上下文的 key。The TestContext
框架使用以下配置参数来构建上下文缓存键:ApplicationContext
-
locations
(来自@ContextConfiguration
) -
classes
(来自@ContextConfiguration
) -
contextInitializerClasses
(来自@ContextConfiguration
) -
contextCustomizers
(from ) – 这包括 Spring Boot 的 testing 支持,例如 和 .ContextCustomizerFactory
@DynamicPropertySource
@MockBean
@SpyBean
-
contextLoader
(来自@ContextConfiguration
) -
parent
(来自@ContextHierarchy
) -
activeProfiles
(来自@ActiveProfiles
) -
propertySourceLocations
(来自@TestPropertySource
) -
propertySourceProperties
(来自@TestPropertySource
) -
resourceBasePath
(来自@WebAppConfiguration
)
例如,如果为 的 (或 ) 属性指定 ,则 TestContext 框架
加载相应的 API 并将其存储在上下文缓存中
在仅基于这些位置的键下。因此,如果还为其位置定义(显式或
隐式地通过继承)但不定义 、 不同 、 不同的活动配置文件、 不同的上下文初始化器、 不同的
test 属性源或不同的父上下文,则两个测试类共享相同的内容。这意味着加载应用程序的设置成本
context 仅发生一次(每个测试套件),并且后续测试执行很多
更快。TestClassA
{"app-config.xml", "test-config.xml"}
locations
value
@ContextConfiguration
ApplicationContext
static
TestClassB
{"app-config.xml", "test-config.xml"}
@WebAppConfiguration
ContextLoader
ApplicationContext
测试套件和分叉流程
Spring TestContext 框架将应用程序上下文存储在静态缓存中。这
表示上下文实际上存储在变量中。换句话说,如果
测试在单独的进程中运行,则在每个测试之间清除静态缓存
execution,这实际上会禁用缓存机制。 要从缓存机制中受益,所有测试都必须在同一进程或测试中运行
套房。这可以通过在 IDE 中作为一个组执行所有测试来实现。同样地
使用 Ant、Maven 或 Gradle 等构建框架执行测试时,它是
请务必确保 Build Framework 不会在测试之间分叉。例如
如果 Maven Surefire 插件的 |
上下文缓存的大小以默认最大大小 32 为界。每当
达到最大大小,则使用最近最少使用 (LRU) 的驱逐策略进行驱逐,并且
关闭过时的上下文。您可以从命令行或内部版本配置最大大小
脚本,方法是设置名为 的 JVM 系统属性。作为
或者,您可以通过 SpringProperties
机制设置相同的属性。spring.test.context.cache.maxSize
由于在给定的测试套件中加载了大量的应用程序上下文
导致套件需要不必要地长时间运行,这通常是有益的
确切地知道已经加载和缓存了多少个上下文。要查看
底层上下文缓存中,您可以将 Logging 类别的 Log level 设置为 。org.springframework.test.context.cache
DEBUG
在不太可能的情况下,测试会损坏应用程序上下文并需要重新加载
(例如,通过修改 Bean 定义或应用程序对象的状态),则
可以用 Comments 你的测试类或测试方法(参见 @DirtiesContext
中的讨论)。这会指示 Spring
在运行之前从缓存中删除上下文并重新构建应用程序上下文
需要相同应用程序上下文的下一个测试。请注意,对 Comments 的支持由 和 提供,默认情况下处于启用状态。@DirtiesContext
@DirtiesContext
@DirtiesContext
DirtiesContextBeforeModesTestExecutionListener
DirtiesContextTestExecutionListener
上下文层次结构
当编写依赖于加载的 Spring 的集成测试时,它是
通常足以针对单个上下文进行测试。但是,有时确实如此
对于针对实例层次结构进行测试有益甚至必要。例如,如果您正在开发 Spring MVC Web 应用程序,则通常
有一个根由 Spring 加载,并且
由 Spring 的 .这会导致
父子上下文层次结构,其中共享组件和基础设施配置
在根上下文中声明,并在子上下文中由 Web 特定的
组件。另一个用例可以在 Spring Batch 应用程序中找到,您经常在其中
具有为共享批处理基础架构提供配置的父上下文,以及
子上下文,用于特定批处理作业的配置。ApplicationContext
ApplicationContext
WebApplicationContext
ContextLoaderListener
WebApplicationContext
DispatcherServlet
您可以通过声明 context 来编写使用上下文层次结构的集成测试
配置,或者在单个测试类上
或在测试类层次结构中。如果在多个类上声明了上下文层次结构
在 Test Class 层次结构中,您还可以合并或覆盖 Context Configuration
对于上下文层次结构中的特定命名级别。合并
层次结构中的给定级别,则配置资源类型(即 XML 配置
文件或组件类)必须一致。否则,完全可以接受
在使用不同的资源类型配置的上下文层次结构中具有不同的级别。@ContextHierarchy
本节中其余基于 JUnit Jupiter 的示例显示了常见配置 需要使用上下文层次结构的集成测试的方案。
ControllerIntegrationTests
表示
Spring MVC Web 应用程序,通过声明一个由两个级别组成的上下文层次结构,
一个用于根(使用类加载),另一个用于 Dispatcher Servlet(使用类加载)。自动连接到测试实例中的是子上下文的 URL(即
层次结构中最低的上下文)。下面的清单显示了此配置场景:WebApplicationContext
TestAppConfig
@Configuration
WebApplicationContext
WebConfig
@Configuration
WebApplicationContext
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
@Autowired
WebApplicationContext wac;
// ...
}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [TestAppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {
@Autowired
lateinit var wac: WebApplicationContext
// ...
}
此示例中的测试类定义测试类中的上下文层次结构
等级制度。 声明 Spring 驱动的 Web 应用程序中根的配置。但请注意,这并不声明 .因此,的子类可以选择参与上下文层次结构或遵循
的标准语义。 以及通过以下方式扩展和定义上下文层次结构
用。结果是加载了三个应用程序上下文(一个
对于每个声明),并加载了应用程序上下文
based on the configuration in 设置为每个 of 的父上下文
为 Concrete 子类加载的上下文。下面的清单显示了这一点
配置场景:AbstractWebTests
WebApplicationContext
AbstractWebTests
@ContextHierarchy
AbstractWebTests
@ContextConfiguration
SoapWebServiceTests
RestWebServiceTests
AbstractWebTests
@ContextHierarchy
@ContextConfiguration
AbstractWebTests
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
@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 层次结构中特定级别的配置。 定义两个级别
在层次结构中,和 . extends 和 instructs
Spring TestContext 框架合并层次结构级别的上下文配置,方法是确保在 中的属性中声明的名称都是。结果是三个应用程序上下文
加载:一个用于 、 一个用于 ,一个用于 。与前面的示例一样,
application context loaded from 设置为
从 和 加载的上下文。
下面的清单显示了此配置场景:BaseTests
parent
child
ExtendedTests
BaseTests
child
name
@ContextConfiguration
child
/app-config.xml
/user-config.xml
{"/user-config.xml", "/order-config.xml"}
/app-config.xml
/user-config.xml
{"/user-config.xml", "/order-config.xml"}
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
@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() {}
与前面的示例相比,此示例演示了如何覆盖
配置,方法是将标志 in 设置为 。因此,
应用程序上下文 仅从 和 加载
将其父级设置为从 .以下清单
显示了此配置方案: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() {}
在上下文层次结构中弄脏上下文 如果您在测试中使用其上下文配置为
context 层次结构中,您可以使用该标志来控制上下文缓存的方式
已清除。有关更多详细信息,请参阅 Spring Testing Annotations 和 @DirtiesContext javadoc 中的讨论。@DirtiesContext hierarchyMode @DirtiesContext |
3.5.6. 测试 Fixture 的依赖注入
当您使用 (由
default),则测试实例的依赖项将从
您配置的应用程序上下文 or related
附注。您可以使用 setter 注入和/或字段注入,具体取决于
您选择哪些 Comments,以及是将它们放在 setter 方法还是 Fields 上。
如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入
(参见 Dependency Injection with SpringExtension
)。为了与 Spring 的基于 Comments 的
injection 支持,你也可以使用 Spring 的 Comments 或 JSR-330 中的 Comments 进行 field 和 setter 注入。DependencyInjectionTestExecutionListener
@ContextConfiguration
@Autowired
@Inject
对于 JUnit Jupiter 以外的测试框架,TestContext 框架不会
参与 Test 类的实例化。因此,使用 OR for 构造函数对测试类没有影响。@Autowired @Inject |
尽管在生产代码中不鼓励使用字段注入,但
实际上在测试代码中很自然。差异的基本原理是您将
永远不要直接实例化你的测试类。因此,无需能够
在测试类上调用 constructor 或 setter 方法。public |
因为 用于执行自动装配
type,如果你有多个相同类型的 bean 定义,则不能依赖 this
方法。在这种情况下,您可以在
结合。您还可以选择与 结合使用。或者,如果您的测试类可以访问其 ,则
可以通过使用(例如)对 .@Autowired
@Autowired
@Qualifier
@Inject
@Named
ApplicationContext
applicationContext.getBean("titleRepository", TitleRepository.class)
如果您不希望将依赖项注入应用于测试实例,请不要注释
字段或 setter 方法与 或 .或者,您可以禁用
依赖项注入,方法是显式配置您的类并从侦听器列表中省略。@Autowired
@Inject
@TestExecutionListeners
DependencyInjectionTestExecutionListener.class
考虑测试类的方案,如 目标 部分所述。接下来的两个代码清单演示了
使用 on fields 和 setter 方法。应用程序上下文配置
显示在所有示例代码清单之后。HibernateTitleRepository
@Autowired
以下代码清单中的依赖关系注入行为并非特定于 JUnit 木星。相同的 DI 技术可以与任何支持的测试结合使用 框架。 以下示例调用静态断言方法,例如
但不在调用前加上 .在这种情况下,假设
通过未显示在
例。 |
第一个代码清单显示了测试类的基于 JUnit Jupiter 的实现,该
场注入的用途:@Autowired
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
@Autowired
HibernateTitleRepository titleRepository;
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@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)
}
}
或者,你可以配置用于 setter 注入的类,如
遵循:@Autowired
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
HibernateTitleRepository titleRepository;
@Autowired
void setTitleRepository(HibernateTitleRepository titleRepository) {
this.titleRepository = titleRepository;
}
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@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)
}
}
前面的代码清单使用注释引用的相同 XML 上下文文件(即 )。以下内容
显示了此配置:@ContextConfiguration
repository-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
<bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- configuration elided for brevity -->
</bean>
</beans>
如果你从 Spring 提供的测试基类扩展,而该基类恰好在其 setter 方法之一上使用,则你可能有多个受影响的 bean
类型(例如,多个 bean)。在
在这种情况下,你可以重写 setter 方法并使用 Comments 来
指示一个特定的目标 Bean,如下所示(但请确保将 delegate 委托给被覆盖的
方法): Java
Kotlin
指定的 qualifier 值指示要注入的特定 bean,
将类型匹配项集缩小到特定的 bean。它的值与相应定义中的声明匹配。bean 名称
用作回退限定符值,因此您还可以有效地指向特定的
bean 按名称排列(如前所述,假设这是 bean)。 |
3.5.7. 测试请求和会话范围的 bean
Spring 支持 Request 和 session-scoped bean 的 bean 中,你可以测试你的 request-scoped 和 session-scoped Beans 执行以下步骤:
-
通过注释测试,确保为测试加载 a 类替换为 .
WebApplicationContext
@WebAppConfiguration
-
将模拟请求或会话注入到测试实例中并准备测试 夹具。
-
调用您从配置的 (使用依赖项注入) 检索的 Web 组件。
WebApplicationContext
-
对 mock 执行断言。
下一个代码片段显示了登录使用案例的 XML 配置。请注意,该 Bean 依赖于请求范围的 Bean。此外,通过使用 SPEL 表达式进行实例化,该表达式
从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望
通过 TestContext 框架管理的 mock 配置这些请求参数。
以下清单显示了此使用案例的配置:userService
loginAction
LoginAction
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:loginAction-ref="loginAction"/>
<bean id="loginAction" class="com.example.LoginAction"
c:username="#{request.getParameter('user')}"
c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy/>
</bean>
</beans>
在 中,我们将 (即
test) 和 test 实例。在我们的测试方法中,我们通过在
提供的 .当在我们的 上调用该方法时,我们可以确保用户服务可以访问当前
set 参数)。然后,我们可以根据已知的
username 和 password 的输入。下面的清单显示了如何做到这一点:RequestScopedBeanTests
UserService
MockHttpServletRequest
requestScope()
MockHttpServletRequest
loginUser()
userService
loginAction
MockHttpServletRequest
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpServletRequest request;
@Test
void requestScope() {
request.setParameter("user", "enigma");
request.setParameter("pswd", "$pr!ng");
LoginResults results = userService.loginUser();
// assert results
}
}
@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
豆。但是,这一次,该 Bean 依赖于会话范围的 Bean。请注意,该 bean 是使用
从当前 HTTP 会话中检索主题的 SPEL 表达式。在我们的测试中,我们
需要在 TestContext 框架管理的 mock session 中配置一个主题。这
以下示例显示了如何执行此操作:userService
userPreferences
UserPreferences
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:userPreferences-ref="userPreferences" />
<bean id="userPreferences" class="com.example.UserPreferences"
c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy/>
</bean>
</beans>
在 中,我们将 和 注入
我们的测试实例。在我们的测试方法中,我们通过以下方式设置我们的测试夹具
在提供的 .当该方法在我们的 上调用时,我们可以确保
用户服务可以访问当前 的会话范围,我们可以根据
配置的主题。以下示例显示了如何执行此操作:SessionScopedBeanTests
UserService
MockHttpSession
sessionScope()
theme
MockHttpSession
processUserPreferences()
userService
userPreferences
MockHttpSession
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpSession session;
@Test
void sessionScope() throws Exception {
session.setAttribute("theme", "blue");
Results results = userService.processUserPreferences();
// assert results
}
}
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired lateinit var userService: UserService
@Autowired lateinit var session: MockHttpSession
@Test
fun sessionScope() {
session.setAttribute("theme", "blue")
val results = userService.processUserPreferences()
// assert results
}
}
3.5.8. 事务管理
在 TestContext 框架中,事务由 默认配置的 管理,即使您没有这样做
在你的测试类上显式声明。要启用对
transactions,但是,您必须在 中配置一个 bean,该 bean 加载了语义(进一步
详细信息将在后面提供)。此外,你必须在测试的类或方法级别声明 Spring 的注释。TransactionalTestExecutionListener
@TestExecutionListeners
PlatformTransactionManager
ApplicationContext
@ContextConfiguration
@Transactional
测试管理的事务
测试托管的事务是通过使用 或 using 以编程方式进行管理的事务(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆
事务(那些由 Spring 直接管理的
测试)或应用程序管理的事务(在
由测试调用的应用程序代码)。Spring 管理和应用程序管理
事务通常参与 test-managed 事务。但是,您应该使用
如果 Spring 管理的事务或应用程序管理的事务配置了任何
propagation type 而不是 or (有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListener
TestTransaction
ApplicationContext
REQUIRED
SUPPORTS
抢占式超时和测试托管事务
在测试框架中使用任何形式的抢占式超时时,必须小心 与 Spring 的 test-managed 事务结合使用。 具体来说, Spring 的测试支持将事务状态绑定到当前线程(通过
一个变量)在调用当前测试方法之前。如果
testing 框架在新线程中调用当前测试方法,以支持
preemptive timeout,则在当前测试方法中执行的任何操作都不会
在测试托管的事务中调用。因此,任何此类操作的结果
不会使用 test-managed 事务回滚。相反,此类操作
将提交到持久化存储(例如,关系数据库),甚至
尽管 Spring 正确回滚了 test-managed 事务。 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
为测试方法添加注释会导致测试在
transaction 的事务,默认情况下,在测试完成后自动回滚。
如果测试类使用 进行批注,则该类中的每个测试方法
hierarchy 在事务中运行。未注释的测试方法(在类或方法级别)不会在事务中运行。注意
这在测试生命周期方法(例如,方法)上不受支持
用 JUnit Jupiter 的 , , 等进行注释。此外,测试
被注释,但将属性设置为 不在事务中运行。@Transactional
@Transactional
@Transactional
@Transactional
@BeforeAll
@BeforeEach
@Transactional
propagation
NOT_SUPPORTED
属性 | 支持测试托管事务 |
---|---|
|
是的 |
|
仅支持 |
|
不 |
|
不 |
|
不 |
|
否:改用 |
|
否:改用 |
方法级生命周期方法(例如,使用 JUnit Jupiter 的 or 注释的方法)在测试托管的事务中运行。另一方面
hand、套件级和类级生命周期方法,例如,带有
使用 TestNG 、 、 或 — 注释的 JUnit Jupiter 的 or 和 方法不在
test-managed 事务。 如果您需要在
transaction,您可能希望将相应的
您的 test 类,然后将其与 for programmatic 一起使用
事务管理。 |
请注意,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
事务回滚和提交行为
默认情况下,测试事务将在完成
测试;但是,事务提交和回滚行为可以通过声明方式进行配置
通过 和 注释。有关更多详细信息,请参阅 annotation support 部分中的相应条目。@Commit
@Rollback
程序化事务管理
您可以使用静态
方法。例如,您可以在 test 中使用
methods、before methods和 after 方法来开始或结束当前的测试托管
transaction 或配置当前测试托管的事务以进行 rollback 或 commit。
只要启用,支持就会自动可用。TestTransaction
TestTransaction
TestTransaction
TransactionalTestExecutionListener
以下示例演示了 .请参阅
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"))
}
}
在事务外部运行代码
有时,您可能需要在事务测试之前或之后运行某些代码
方法,但在事务上下文之外,例如,要验证初始
数据库状态
行为(如果测试配置为提交事务)。 支持此类方案的 AND 注释。您可以使用以下方法之一对测试类中的任何方法或测试接口中的任何默认方法进行注释
annotations 的 API 中,并确保
transaction method 或在 transaction method 在适当的时间运行之后。TransactionalTestExecutionListener
@BeforeTransaction
@AfterTransaction
void
void
TransactionalTestExecutionListener
任何 before 方法(例如用 JUnit Jupiter 注释的方法)
和任何 after 方法(例如用 JUnit Jupiter's 注释的方法)都是
run 在事务中。此外,对于未配置为在
交易。@BeforeEach @AfterEach @BeforeTransaction @AfterTransaction |
配置事务管理器
TransactionalTestExecutionListener
期望 bean 为
在 Spring 中为测试定义。如果有多个实例
of 中,您可以声明一个
限定符,或者可以由类实现。查阅 javadoc
对于 TestContextTransactionUtils.retrieveTransactionManager(),
有关
算法用于在测试的 .PlatformTransactionManager
ApplicationContext
PlatformTransactionManager
ApplicationContext
@Transactional("myTxMgr")
@Transactional(transactionManager =
"myTxMgr")
TransactionManagementConfigurer
@Configuration
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
|
3.5.9. 执行 SQL 脚本
在针对关系数据库编写集成测试时,通常
运行 SQL 脚本以修改数据库架构或将测试数据插入表中。该模块支持初始化嵌入式或现有数据库
通过在加载 Spring 时执行 SQL 脚本。请参阅嵌入式数据库支持和使用
embedded 数据库。spring-jdbc
ApplicationContext
尽管在加载时初始化数据库以进行测试一次非常有用,但有时必须能够修改
数据库。以下部分介绍如何运行 SQL
在集成测试期间以编程和声明方式编写脚本。ApplicationContext
以编程方式执行 SQL 脚本
Spring 提供了以下选项,用于在 集成测试方法。
-
org.springframework.jdbc.datasource.init.ScriptUtils
-
org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
-
org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
-
org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils
提供用于处理 SQL 的静态实用程序方法的集合
脚本,主要供框架内部使用。但是,如果您
需要完全控制 SQL 脚本的解析和运行方式,可能适合
您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc
方法中的更多详细信息。ScriptUtils
ScriptUtils
ResourceDatabasePopulator
提供基于对象的 API,用于以编程方式填充,
使用外部
资源。 提供用于配置字符的选项
encoding、语句分隔符、注释分隔符和错误处理标志
解析并运行脚本。每个配置选项都有一个合理的
default 值。请参阅 javadoc 以获取
有关默认值的详细信息。要运行在 中配置的脚本,可以调用
针对 a 或
对 .以下示例
为测试架构和测试数据指定 SQL 脚本,将语句分隔符设置为 ,然后针对 :ResourceDatabasePopulator
ResourceDatabasePopulator
populate(Connection)
java.sql.Connection
execute(DataSource)
javax.sql.DataSource
@@
DataSource
@Test
void databaseTest() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(
new ClassPathResource("test-schema.sql"),
new ClassPathResource("test-data.sql"));
populator.setSeparator("@@");
populator.execute(this.dataSource);
// run code that uses the test schema and data
}
@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
}
请注意,内部委托 to 进行解析
以及运行 SQL 脚本。类似地,AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
中的方法在内部使用a来运行 SQL 脚本。请参阅 Javadoc 以获取
各种方法了解更多详情。ResourceDatabasePopulator
ScriptUtils
executeSqlScript(..)
ResourceDatabasePopulator
executeSqlScript(..)
使用 @Sql 以声明方式执行 SQL 脚本
除了上述以编程方式运行 SQL 脚本的机制外,
你可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。
具体来说,您可以在测试类或测试方法上声明注释,以
配置单个 SQL 语句或 SQL 脚本的资源路径,这些脚本应该是
在集成测试方法之前或之后针对给定数据库运行。支持由 提供,默认情况下处于启用状态。@Sql
@Sql
SqlScriptsTestExecutionListener
默认情况下,方法级声明会覆盖类级声明。如
但是,此行为可以按测试类或
测试方法通过 .有关更多详细信息,请参阅使用 @SqlMergeMode 合并和覆盖配置。@Sql @SqlMergeMode |
路径资源语义
每个 path 都解释为 Spring 。普通路径(例如 )被视为相对于
定义测试类。以斜杠开头的路径被视为绝对路径
类路径资源(例如 )。引用
URL(例如,前缀为 , , 的路径)是使用
指定的资源协议。Resource
"schema.sql"
"/org/example/schema.sql"
classpath:
file:
http:
以下示例演示如何在类级别和方法级别使用
在基于 JUnit Jupiter 的集成测试类中:@Sql
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that uses the test schema and test data
}
}
@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,则抛出 an。default
@Sql
IllegalStateException
-
类级声明:如果带注解的测试类是 ,则 相应的默认脚本为 。
com.example.MyTest
classpath:com/example/MyTest.sql
-
方法级声明:如果带注释的测试方法已命名且为 在 类 中定义,对应的默认脚本为 。
testMethod()
com.example.MyTest
classpath:com/example/MyTest.testMethod.sql
声明多个集@Sql
如果需要为给定的测试类或测试配置多组 SQL 脚本
方法,但具有不同的语法配置、不同的错误处理规则,或者
每个集合的不同执行阶段,您可以声明多个 .跟
Java 8 中,您可以用作可重复的注释。否则,可以将 Comments 用作显式容器来声明 .@Sql
@Sql
@SqlGroup
@Sql
以下示例显示了如何用作 Java 8 的可重复注释:@Sql
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
在前面示例所示的场景中,该脚本使用
单行注释的不同语法。test-schema.sql
以下示例与前面的示例相同,只是声明在 .在 Java 8 及更高版本中,使用 of 是可选的,但您可能需要使用 for compatibility with
其他 JVM 语言,例如 Kotlin。@Sql
@SqlGroup
@SqlGroup
@SqlGroup
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
@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
您可以使用注释配置脚本解析和错误处理。
当声明为集成测试类上的类级注释时,用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候
使用 Comments 的属性直接声明,用作在封闭 Comments 中声明的 SQL 脚本的本地配置。中的每个属性都有一个隐式默认值,即
记录在相应属性的 javadoc 中。由于为
annotation 属性,遗憾的是,它不是
可以将值 of 分配给 Annotation 属性。因此,为了
支持覆盖继承的全局配置,属性具有
显式默认值 (for Strings)、(for Arrays) 或 (for
枚举)。这种方法允许 的本地声明选择性地覆盖
通过提供值 Other 来自全局声明的单个属性
than 、 或 .全局属性在何时继承
local 属性不提供除 、 、 或 以外的显式值。因此,显式本地配置将覆盖全局配置。@SqlConfig
@SqlConfig
config
@Sql
@SqlConfig
@Sql
@SqlConfig
null
@SqlConfig
""
{}
DEFAULT
@SqlConfig
@SqlConfig
""
{}
DEFAULT
@SqlConfig
@SqlConfig
""
{}
DEFAULT
提供的配置选项与 和 等效
supported by and but 是这些
由 XML 命名空间元素提供。请参阅 javadoc
有关详细信息,请参阅 @Sql
和 @SqlConfig
中的 individual attributes。@Sql
@SqlConfig
ScriptUtils
ResourceDatabasePopulator
<jdbc:initialize-database/>
@Sql
的事务管理
默认情况下,会推断出所需的事务
使用 配置的脚本的语义。具体来说,SQL 脚本是运行的
如果没有事务,则在现有的 Spring 管理的事务中(例如,
事务 管理,或者管理在隔离的事务中,具体取决于配置的值
的属性 in 和测试的 .作为最低限度,
但是,A 必须存在于测试的 .SqlScriptsTestExecutionListener
@Sql
TransactionalTestExecutionListener
@Transactional
transactionMode
@SqlConfig
PlatformTransactionManager
ApplicationContext
javax.sql.DataSource
ApplicationContext
如果 用于检测 和 并推断交易语义的算法不符合您的需求,
您可以通过设置 的 and 属性来指定显式名称。此外,您可以控制事务传播
行为(例如,是否
脚本应在隔离的事务中运行)。虽然对所有
事务管理支持的选项 is not the scope of this
参考手册中,@SqlConfig
和 SqlScriptsTestExecutionListener
的 javadoc 提供了详细信息,以下示例显示了一个典型的测试场景
它使用 JUnit Jupiter 和事务测试:SqlScriptsTestExecutionListener
DataSource
PlatformTransactionManager
dataSource
transactionManager
@SqlConfig
transactionMode
@SqlConfig
@Sql
@Sql
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
final JdbcTemplate jdbcTemplate;
@Autowired
TransactionalSqlScriptsTests(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
@Sql("/test-data.sql")
void usersTest() {
// verify state in test database:
assertNumUsers(2);
// run code that uses the test data...
}
int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
void assertNumUsers(int expected) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.");
}
}
@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.")
}
}
合并和覆盖配置@SqlMergeMode
从 Spring Framework 5.2 开始,可以将方法级声明与
类级声明。例如,这允许您为
数据库架构或一些常见的测试数据,然后提供额外的
每个测试方法的用例特定测试数据。要启用合并,请注释
您的 test 类或 test 方法与 .要禁用
Specific Test Method(或Specific Test Subclass),可以切换回默认模式
通过。请参阅 @SqlMergeMode
注释文档部分 有关示例和更多详细信息。@Sql
@Sql
@SqlMergeMode(MERGE)
@SqlMergeMode(OVERRIDE)
3.5.10. 并行测试执行
Spring Framework 5.0 引入了对在 单个 JVM。一般来说,这意味着大多数 测试类或测试方法可以并行运行,而无需对测试代码进行任何更改 或 configuration。
有关如何设置并行测试执行的详细信息,请参阅 测试框架、构建工具或 IDE。 |
请记住,将并发引入测试套件可能会导致 意外的副作用、奇怪的运行时行为以及间歇性失败的测试,或者 似乎是随机的。因此,Spring Team 提供了以下一般准则 for when 不并行运行测试。
如果测试符合以下条件,则不要并行运行测试:
-
使用 Spring Framework 的支持。
@DirtiesContext
-
使用 Spring Boot 的 or 支持。
@MockBean
@SpyBean
-
使用 JUnit 4 的支持或任何测试框架功能 旨在确保测试方法按特定顺序运行。注意 但是,如果整个测试类并行运行,则这不适用。
@FixMethodOrder
-
更改共享服务或系统的状态,例如数据库、消息代理、 filesystem 等。这适用于嵌入式系统和外部系统。
如果并行测试执行失败,并出现异常,指出当前测试的 不再处于活动状态,这通常意味着 已从其他线程中删除。 这可能是由于使用 或由于 自动驱逐 造成的。如果是罪魁祸首,您需要找到一种方法
避免使用此类测试或从并行执行中排除此类测试。如果
已超出 的最大大小,您可以增加最大大小
缓存中。有关详细信息,请参阅 context caching 上的讨论。 |
只有在以下情况下,才能在 Spring TestContext 框架中并行执行测试
底层实现提供了一个 Copy 构造函数,如
TestContext 的 javadoc。Spring 中使用的提供了这样的构造函数。但是,如果您使用
提供自定义实现的第三方库,您需要
验证它是否适合并行测试执行。TestContext DefaultTestContext TestContext |
3.5.11. TestContext 框架支持类
本节描述了支持 Spring TestContext 框架的各种类。
Spring JUnit 4 运行程序
Spring TestContext 框架通过自定义
runner(在 JUnit 4.12 或更高版本上受支持)。通过使用 或 更短的变体注释测试类,开发人员可以实现基于 JUnit 4 的标准单元和集成测试,以及
同时获得 TestContext 框架的好处,例如支持
加载应用程序上下文, 测试实例的依赖注入, 事务测试
method 执行,依此类推。如果你想将 Spring TestContext 框架与
替代运行程序(例如 JUnit 4 的运行程序)或第三方运行程序
(例如),您可以选择改用 Spring 对 JUnit 规则的支持。@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
Parameterized
MockitoJUnitRunner
以下代码清单显示了将测试类配置为
使用自定义的 Spring 运行:Runner
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
在前面的示例中,配置了一个空列表,以
禁用默认侦听器,否则需要 TO
通过 进行配置。@TestExecutionListeners
ApplicationContext
@ContextConfiguration
Spring JUnit 4 规则
该软件包提供以下 JUnit
4 个规则(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4.rules
-
SpringClassRule
-
SpringMethodRule
SpringClassRule
是一个 JUnit,它支持 Spring 的类级功能
TestContext Framework,而是一个支持
Spring TestContext 框架的实例级和方法级功能。TestRule
SpringMethodRule
MethodRule
与 相比,Spring 基于规则的 JUnit 支持具有以下优点
独立于任何实现,因此可以是
与现有的替代运行程序(如 JUnit 4 )组合,或
第三方运行程序(例如 )。SpringRunner
org.junit.runner.Runner
Parameterized
MockitoJUnitRunner
要支持 TestContext 框架的全部功能,必须将 a 与 .以下示例显示了正确的方法
要在集成测试中声明这些规则: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 支持类
该软件包提供以下支持
类(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4
-
AbstractJUnit4SpringContextTests
-
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是一个抽象基测试类,它集成了
Spring TestContext 框架,在
JUnit 4 环境中。扩展 时,可以访问可用于执行显式
bean 查找或测试整个上下文的状态。ApplicationContext
AbstractJUnit4SpringContextTests
protected
applicationContext
AbstractTransactionalJUnit4SpringContextTests
是一个抽象的事务扩展,它为 JDBC 添加了一些方便的功能
访问。此类期望在 .当您
extend 中,您可以访问一个实例变量,该变量可用于运行 SQL 语句来查询
数据库。您可以使用此类查询来确认之前和之后的数据库状态
运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在
与应用程序代码相同的事务的范围。当与
一个 ORM 工具,一定要避免误报。
如 JDBC 测试支持中所述,还提供了以下便捷方法:
委托给 中的方法。
此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractJUnit4SpringContextTests
javax.sql.DataSource
PlatformTransactionManager
ApplicationContext
AbstractTransactionalJUnit4SpringContextTests
protected
jdbcTemplate
AbstractTransactionalJUnit4SpringContextTests
JdbcTestUtils
jdbcTemplate
AbstractTransactionalJUnit4SpringContextTests
executeSqlScript(..)
DataSource
这些类便于扩展。如果您不需要您的测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类
JUnit 规则。@RunWith(SpringRunner.class) |
用于 JUnit Jupiter 的 SpringExtension
Spring TestContext 框架提供了与 JUnit Jupiter 测试的完全集成
框架,在 JUnit 5 中引入。通过使用 注释测试类,您可以实现基于 Jupiter 的标准 JUnit 单元
和集成测试,同时获得 TestContext 框架的好处,
例如支持加载应用程序上下文、测试实例的依赖注入、
事务性测试方法执行,等等。@ExtendWith(SpringExtension.class)
此外,由于 JUnit Jupiter 中丰富的扩展 API,Spring 提供了 Spring 支持 JUnit 4 和 TestNG 的:
-
测试构造函数、测试方法和测试生命周期回调的依赖项注入 方法。有关更多详细信息,请参见 Dependency Injection with
SpringExtension
。 -
对条件的强大支持 基于 SpEL 表达式、环境变量、系统属性的测试执行、 等等。有关更多详细信息和示例,请参见 Spring JUnit Jupiter Testing Annotations 中的文档。
@EnabledIf
@DisabledIf
-
自定义组合的注释,结合了来自 Spring 和 JUnit Jupiter 的注释。看 和 examples 在 Meta-Annotation Support for Testing 中了解更多详细信息。
@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 提供了 和 组合的注释来简化
测试和 JUnit Jupiter 的配置。@SpringJUnitConfig
@SpringJUnitWebConfig
ApplicationContext
以下示例用于减少配置量
在前面的例子中使用:@SpringJUnitConfig
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
// 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...
}
}
同样,以下示例用于创建用于 JUnit Jupiter 的 API:@SpringJUnitWebConfig
WebApplicationContext
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// test logic...
}
}
// 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...
}
}
有关更多详细信息,请参见 Spring JUnit Jupiter Testing Annotations 中的文档。@SpringJUnitConfig
@SpringJUnitWebConfig
依赖项注入SpringExtension
SpringExtension
从 JUnit Jupiter 实现 ParameterResolver
扩展 API,它允许 Spring 为测试提供依赖注入
构造函数、测试方法和测试生命周期回调方法。
具体来说,可以将测试中的依赖项注入到用 、 等注释的测试构造函数和方法中。SpringExtension
ApplicationContext
@BeforeAll
@AfterAll
@BeforeEach
@AfterEach
@Test
@RepeatedTest
@ParameterizedTest
构造函数注入
如果 JUnit Jupiter 测试类的构造函数中的特定参数是类型(或其子类型)或用、、 或 、 或 进行 Meta 注释或元注释,则 Spring 会注入该特定
参数替换为测试的 .ApplicationContext
@Autowired
@Qualifier
@Value
ApplicationContext
如果 Spring 如果 构造函数被认为是可自动布线的。构造函数被视为 如果满足以下条件之一,则为 autowirable(按优先顺序)。
-
构造函数用 .
@Autowired
-
@TestConstructor
在测试类上存在或元存在,且属性设置为 。autowireMode
ALL
-
默认测试构造函数 autowire 模式已更改为 。
ALL
有关使用和如何更改全局测试构造函数 autowire 模式的详细信息,请参见 @TestConstructor
。@TestConstructor
如果测试类的构造函数被认为是可自动布线的,则 Spring
承担解析构造函数中所有参数的参数的责任。
因此,在 JUnit Jupiter 中注册的其他 API 都无法解析
参数。ParameterResolver |
测试类的构造函数注入不能与 JUnit 结合使用
Jupiter 的 support if 用于关闭
测试的 Before 或 After 测试方法。 原因是指示 JUnit Jupiter 缓存测试
实例。因此,测试实例将保留
对最初从
随后关闭。由于测试类的构造函数只会被调用
一旦在这种情况下,依赖注入将不会再次发生,后续测试
将与来自 closed 的 bean 交互,这可能会导致错误。 与 “before test method” 或 “after test method” 模式一起使用
与 结合使用,必须从 Spring 配置依赖项
通过 field 或 setter 注入提供,以便它们可以在测试之间重新注入
方法调用。 |
在下面的示例中, Spring 将 Bean 从加载的 from 注入到构造函数中。OrderService
ApplicationContext
TestConfig.class
OrderServiceIntegrationTests
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
// tests that use the injected OrderService
}
请注意,此功能允许测试依赖项是不可变的,因此是不可变的。final
如果属性是 to (参见 @TestConstructor
),我们可以省略上一个例子中构造函数的声明 of,从而产生以下内容。spring.test.constructor.autowire.mode
all
@Autowired
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
// tests that use the injected OrderService
}
方法注入
如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数为
类型(或其子类型)或用 、 、 或 、 、 进行 Meta 注释或元注释的 Spring 注入该特定
参数替换为测试的 .ApplicationContext
@Autowired
@Qualifier
@Value
ApplicationContext
在下面的示例中, Spring 将 from the loaded from 注入到测试方法中: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
}
}
由于 JUnit Jupiter 中支持的健壮性,您还可以
将多个依赖项注入到单个方法中,不仅来自 Spring,而且来自
从 JUnit Jupiter 本身或其他第三方扩展。ParameterResolver
以下示例显示了如何同时让 Spring 和 JUnit Jupiter 注入依赖项
同时进入测试方法。placeOrderRepeatedly()
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
@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
}
}
请注意,使用 from JUnit Jupiter 可以让测试方法获得访问权限
到 .@RepeatedTest
RepetitionInfo
TestNG 支持类
该软件包提供以下支持
类:org.springframework.test.context.testng
-
AbstractTestNGSpringContextTests
-
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
是一个抽象基测试类,它集成了
Spring TestContext 框架,在
TestNG 环境。扩展 时,可以访问可用于执行显式
bean 查找或测试整个上下文的状态。ApplicationContext
AbstractTestNGSpringContextTests
protected
applicationContext
AbstractTransactionalTestNGSpringContextTests
是一个抽象的事务扩展,它为 JDBC 添加了一些方便的功能
访问。此类期望在 .当您
extend 中,您可以访问一个实例变量,该变量可用于运行 SQL 语句来查询
数据库。您可以使用此类查询来确认之前和之后的数据库状态
运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在
与应用程序代码相同的事务的范围。当与
一个 ORM 工具,一定要避免误报。
如 JDBC 测试支持中所述,还提供了以下便捷方法:
委托给 中的方法。
此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractTestNGSpringContextTests
javax.sql.DataSource
PlatformTransactionManager
ApplicationContext
AbstractTransactionalTestNGSpringContextTests
protected
jdbcTemplate
AbstractTransactionalTestNGSpringContextTests
JdbcTestUtils
jdbcTemplate
AbstractTransactionalTestNGSpringContextTests
executeSqlScript(..)
DataSource
这些类便于扩展。如果您不需要您的测试类
要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试
类 通过使用 、 、 依此类推 和
使用 .查看源代码
of 来了解如何检测测试类的示例。@ContextConfiguration @TestExecutionListeners TestContextManager AbstractTestNGSpringContextTests |
3.6. Spring MVC 测试框架
Spring MVC 测试框架为测试 Spring MVC 代码提供了一流的支持
使用可与 JUnit、TestNG 或任何其他测试框架一起使用的 Fluent API。它
构建在模块中的 Servlet API 模拟对象之上,因此不使用正在运行的 Servlet 容器。它
使用 提供完整的 Spring MVC 运行时行为,并提供
支持在
除了独立模式之外,在独立模式下,您可以手动实例化控制器并进行测试
一次一个。spring-test
DispatcherServlet
Spring MVC Test 还为测试使用 .客户端测试模拟服务器响应,也不使用正在运行的
服务器。RestTemplate
Spring Boot 提供了一个选项来编写完整的端到端集成测试,这些测试 包括一个正在运行的服务器。如果这是您的目标,请参阅 Spring Boot 参考指南。 有关容器外和端到端之间差异的更多信息 集成测试,请参见 Spring MVC Test vs End-to-End Tests。 |
3.6.1. 服务器端测试
您可以使用 JUnit 或 TestNG 为 Spring MVC 控制器编写简单的单元测试。自
这样做,实例化控制器,为其注入 mocked 或 stubbed 依赖项,以及
调用其方法(传递 、 、 和
其他的,如有必要)。但是,在编写这样的单元测试时,还有很多内容未经过测试:对于
示例、请求映射、数据绑定、类型转换、验证等等。
此外,其他控制器方法(如 , 和 )也可以作为请求处理生命周期的一部分调用。MockHttpServletRequest
MockHttpServletResponse
@InitBinder
@ModelAttribute
@ExceptionHandler
Spring MVC Test 的目标是提供一种有效的方法来测试控制器:
通过实际的 .DispatcherServlet
Spring MVC Test 建立在熟悉的 “mock” 实现之上
模块中提供的 Servlet API。这允许执行请求
以及无需在 Servlet 容器中运行即可生成响应。对于
大多数情况下,一切都应该像运行时一样工作,但有一些值得注意的例外,例如
在 Spring MVC 测试与端到端测试中进行了解释。以下 JUnit
基于 Jupiter 的示例使用 Spring MVC Test:spring-test
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.;
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
void getAccount() throws Exception {
this.mockMvc.perform(get("/accounts/1")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
import org.springframework.test.web.servlet.get
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class ExampleTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
@Test
fun getAccount() {
mockMvc.get("/accounts/1") {
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
}
}
}
Kotlin 中提供了专用的 MockMvc DSL |
前面的测试依赖于 TestContext 的支持
框架从位于同一
package 作为测试类,但基于 Java 和基于 Groovy 的配置也是
支持。请参阅这些样本测试。WebApplicationContext
该实例用于执行请求并验证
生成的响应的状态为 200,内容类型为 ,并且
响应正文具有一个名为值的 JSON 属性。该语法通过 Jayway JsonPath 支持
项目。验证已执行请求结果的许多其他选项包括
本文档稍后将讨论。MockMvc
GET
/accounts/1
application/json
name
Lee
jsonPath
静态导入
上一节示例中的 Fluent API 需要一些静态导入,例如 、 和 。简单的查找方法
这些类用于搜索与 匹配的类型。如果您使用 Eclipse 或 Spring Tools for Eclipse,请务必将它们添加为
Java → Editor 下的 Eclipse 首选项→ Content Assist → Favorites。这样做
允许您在键入静态方法名称的第一个字符后使用 Content Assist。
其他 IDE(例如 IntelliJ)可能不需要任何其他配置。检查
支持对 Static 成员进行代码完成。MockMvcRequestBuilders.*
MockMvcResultMatchers.*
MockMvcBuilders.*
MockMvc*
设置选项
创建 实例有两个主要选项。首先是加载
通过 TestContext 框架进行 Spring MVC 配置,该框架加载 Spring
配置,并将 a 注入到测试中以用于构建实例。以下示例显示了如何执行此操作:MockMvc
WebApplicationContext
MockMvc
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
第二种选择是手动创建一个控制器实例,而不加载 Spring 配置。相反,基本的默认配置与 MVC JavaConfig 或 MVC 命名空间。您可以将其自定义为 度。以下示例显示了如何执行此操作:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
class MyWebTests {
lateinit var mockMvc : MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
}
// ...
}
您应该使用哪个设置选项?
这会加载您的实际 Spring MVC 配置,从而产生一个
完成集成测试。由于 TestContext 框架缓存了加载的 Spring
配置中,它有助于保持测试快速运行,即使您在
测试套件。此外,你可以通过 Spring 将 mock 服务注入控制器
配置以继续专注于测试 Web 层。以下示例声明
带有 Mockito 的 mock 服务:webAppContextSetup
<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()
}
// ...
}
另一方面,的 更接近于单元测试。它测试一个
controller 的 Controller。你可以手动注入控制器的 mock 依赖项,并且
它不涉及加载 Spring 配置。此类测试更侧重于风格
并更容易查看正在测试的控制器,是否有任何特定的 Spring
需要 MVC 配置才能工作,依此类推。这也是一个非常
编写临时测试以验证特定行为或调试问题的便捷方法。standaloneSetup
standaloneSetup
与大多数“集成与单元测试”的争论一样,没有对错之分
答。但是,使用 the 确实意味着需要额外的测试来验证您的 Spring MVC 配置。
或者,您可以使用 编写所有测试,以便始终
针对你的实际 Spring MVC 配置进行测试。standaloneSetup
webAppContextSetup
webAppContextSetup
设置功能
无论你使用哪个 MockMvc 构建器,所有实现都提供
一些常见且非常有用的功能。例如,您可以为
all 请求,预期状态为 200 以及 all 中的标头
响应,如下所示:MockMvcBuilder
Accept
Content-Type
// static import of MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
此外,第三方框架(和应用程序)可以预先打包设置
指令,例如 .Spring Framework 有一个这样的
内置实现,有助于在请求之间保存和重用 HTTP 会话。
您可以按如下方式使用它:MockMvcConfigurer
// 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
有关所有 MockMvc 构建器功能的列表,请参阅 ConfigurableMockMvcBuilder
的 javadoc,或使用 IDE 探索可用选项。
执行请求
您可以执行使用任何 HTTP 方法的请求,如下例所示:
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
accept = MediaType.APPLICATION_JSON
}
您还可以执行内部使用的文件上传请求,以便不会实际解析分段
请求。相反,您必须将其设置为类似于以下示例:MockMultipartHttpServletRequest
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
import org.springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
file("a1", "ABC".toByteArray(charset("UTF8")))
}
您可以在 URI 模板样式中指定查询参数,如下例所示:
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
mockMvc.get("/hotels?thing={thing}", "somewhere")
您还可以添加表示 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 进行测试,请确保相应地设置 and,以便请求映射正常工作,如下例所示
显示: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"
}
在前面的示例中,为每个执行的请求设置 and 会很麻烦。相反,您可以设置默认请求
属性,如下例所示:contextPath
servletPath
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
上述属性会影响通过实例执行的每个请求。
如果在给定请求中也指定了相同的属性,它将覆盖默认值
价值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为
必须在每个请求中指定它们。MockMvc
定义期望
您可以通过在以下位置附加一个或多个调用来定义预期
执行请求,如下例所示:.andExpect(..)
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
status().isOk()
}
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")
}
}
很多时候,在编写测试时,转储执行的
请求。您可以按如下方式执行此操作,其中 is a static import from :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")
}
}
只要请求处理不会导致未处理的异常,该方法
将所有可用的结果数据打印到 。还有一个 method 和
该方法的两个其他变体,一个接受 和
一个接受 .例如,调用 会打印结果
data 复制到 ,而调用时将结果数据打印到自定义
作家。如果要记录结果数据而不是打印结果数据,可以调用该方法,该方法将结果数据记录为 logging 类别下的单个消息。print()
System.out
log()
print()
OutputStream
Writer
print(System.err)
System.err
print(myWriter)
log()
DEBUG
org.springframework.test.web.servlet.result
在某些情况下,您可能希望直接访问结果并验证
否则无法验证。这可以通过在
其他期望,如下例所示:.andReturn()
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn()
// ...
如果所有测试都重复相同的期望值,则可以在以下情况下设置一次通用期望值
构建实例,如下例所示:MockMvc
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
请注意,通用期望始终适用,如果没有
创建单独的实例。MockMvc
当 JSON 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 JsonPath 表达式生成的链接,如下例所示:
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
mockMvc.get("/people") {
accept(MediaType.APPLICATION_JSON)
}.andExpect {
jsonPath("$.links[?(@.rel == 'self')].href") {
value("http://localhost:8080/people")
}
}
当 XML 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 XPath 表达式生成的链接:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
accept(MediaType.APPLICATION_XML)
}.andExpect {
xpath("/person/ns:link[@rel='self']/@href", ns) {
string("http://localhost:8080/people")
}
}
异步请求
Spring MVC 中支持的 Servlet 3.0 异步请求通过退出 Servlet 容器来工作 thread 并允许应用程序异步计算响应,之后 进行异步分派以完成对 Servlet 容器线程的处理。
在 Spring MVC Test 中,可以通过断言生成的 async 值来测试异步请求
首先,然后手动执行异步调度,最后验证响应。
下面是返回 、 的控制器方法的示例测试
或反应式类型,例如 Reactor :DeferredResult
Callable
Mono
@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 | 验证最终响应 |
流式响应
Spring MVC Test 中没有内置用于流的无容器测试的选项
反应。使用 Spring MVC 流选项的应用程序可以使用 WebTestClient 来执行端到端的集成
针对正在运行的服务器进行测试。这在 Spring Boot 中也受支持,您可以在其中使用 .另一个优点是能够使用 from
项目 Reactor 允许对数据流声明期望。WebTestClient
StepVerifier
筛选器注册
在设置实例时,您可以注册一个或多个 Servlet 实例,如下例所示:MockMvc
Filter
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
已注册的过滤器通过 from 调用,而
last 过滤器将 .MockFilterChain
spring-test
DispatcherServlet
Spring MVC 测试与端到端测试
Spring MVC Test 构建在模块的 Servlet API 模拟实现之上,不依赖于正在运行的容器。因此,有
与使用实际
客户端和正在运行的实时服务器。spring-test
考虑这个问题的最简单方法是从空白 .
无论你添加什么,请求都会变成什么。可能会让您感到意外的事情
是默认情况下没有上下文路径;没有 cookie;无转发,
error 或 async dispatches;因此,没有实际的 JSP 呈现。相反
“forwarded” 和 “redirected” URL 保存在 和 可以
带着期望断言。MockHttpServletRequest
jsessionid
MockHttpServletResponse
这意味着,如果您使用 JSP,则可以验证请求所指向的 JSP 页面
转发,但未呈现任何 HTML。换句话说,不调用 JSP。注意
但是,所有其他不依赖于转发的渲染技术(例如
Thymeleaf 和 Freemarker 按预期将 HTML 呈现到响应正文。同样的情况
用于通过方法呈现 JSON、XML 和其他格式。@ResponseBody
或者,您可以考虑从
带有 .请参阅 Spring Boot 参考指南。@SpringBootTest
每种方法都有优点和缺点。Spring MVC Test 中提供的选项包括
从经典的单元测试到完整的集成测试,规模上的站点各不相同。成为
当然,Spring MVC Test 中的任何选项都不属于经典单元的范畴
测试,但他们离它更近一些。例如,您可以隔离 Web 图层
通过将模拟服务注入控制器,在这种情况下,您正在测试 Web
层仅通过 但具有实际的 Spring 配置,因为
可能会独立于其上方的层测试数据访问层。此外,您还可以使用
独立设置,一次专注于一个控制器,并手动提供
使其正常工作所需的配置。DispatcherServlet
使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的 tests 是服务器端的,因此如果出现异常,您可以检查使用了哪个处理程序 使用 HandlerExceptionResolver 处理,模型的内容是什么,绑定是什么 那里有错误,还有其他细节。这意味着编写 expectations 更容易, 因为服务器不是一个不透明的盒子,就像通过实际的 HTTP 测试它时一样 客户。这通常是经典单元测试的一个优势:它更容易编写, reason 和 debug 的 SET 的 SET 的 TEST 的 SET 的 TEST 的 SET 的 U 的 U在 同时,重要的是不要忽视一个事实,即反应是最 需要检查的重要事项。简而言之,这里有多种风格和策略的空间 的测试。
更多示例
框架自己的测试包括许多
示例测试旨在展示如何使用 Spring MVC 测试。您可以浏览以下示例
以获取更多的想法。此外,spring-mvc-showcase
项目具有
基于 Spring MVC 测试的完整测试覆盖率。
3.6.2. HtmlUnit 集成
MockMvc 使用不依赖于 Servlet 容器的模板技术 (例如,Thymeleaf、FreeMarker 等),但它不适用于 JSP,因为 它们依赖于 Servlet 容器。 |
为什么选择 HtmlUnit 集成?
我想到的最明显的问题是“我为什么需要这个?答案是
最好通过浏览一个非常基本的示例应用程序来找到。假设您有一个 Spring MVC Web
支持对对象执行 CRUD 操作的应用程序。该应用程序还
支持对所有消息进行分页。您将如何进行测试?Message
使用 Spring MVC Test,我们可以轻松测试是否能够创建一个 ,如下所示:Message
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
@Test
fun test() {
mockMvc.post("/messages/") {
param("summary", "Spring Rocks")
param("text", "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
}
如果我们想测试允许我们创建消息的表单视图,该怎么办?例如 假设我们的表单类似于以下代码段:
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
我们如何确保我们的表单生成正确的请求来创建新消息?一个 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() }
}
此测试有一些明显的缺点。如果我们更新控制器以使用参数而不是 ,我们的表单测试将继续通过,即使 HTML 表单
与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,因为
遵循:message
text
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
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 设置
首先,确保您已包含 .为了将 HtmlUnit 与 Apache HttpComponents 一起使用
4.5+ 时,您需要使用 HtmlUnit 2.18 或更高版本。net.sourceforge.htmlunit:htmlunit
我们可以通过使用 轻松创建一个与 MockMvc 集成的 HtmlUnit,如下所示:WebClient
MockMvcWebClientBuilder
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
这是使用 .对于高级用法,
参见 Advanced MockMvcWebClientBuilder 。MockMvcWebClientBuilder |
这可确保引用为服务器的任何 URL 都定向到我们的实例,而无需真正的 HTTP 连接。任何其他 URL 为
像往常一样使用网络连接请求。这让我们可以轻松地测试
CDN 的localhost
MockMvc
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
在到目前为止的示例中,我们以最简单的方式使用
可能,通过构建一个基于加载的
Spring TestContext 框架。以下示例中重复了此方法:MockMvcWebClientBuilder
WebClient
WebApplicationContext
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
这更详细,但是,通过构建 with 实例,我们有
MockMvc 的全部功能触手可及。WebClient
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)
那么,如果我们将 改为 ?这样做会迫使我们更新所有
的测试中纳入此更改。这违反了 DRY 原则,所以我们应该
理想情况下,将此代码提取到其自己的方法中,如下所示:id
smmry
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
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 时不必更新所有测试。
我们甚至可以更进一步,将这个逻辑放在 that
表示我们当前所在的位置,如下例所示:Object
HtmlPage
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
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
我们可以使用下面的示例轻松创建与 MockMvc 集成的 Selenium WebDriver:MockMvcHtmlUnitDriverBuilder
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
这是使用 .对于更高级的
用法,请参阅 Advanced MockMvcHtmlUnitDriverBuilder 。MockMvcHtmlUnitDriverBuilder |
前面的示例确保引用为服务器的任何 URL 都是
定向到我们的实例,而无需真正的 HTTP 连接。任何其他
像往常一样,使用网络连接请求 URL。这让我们可以轻松地测试
使用 CDN。localhost
MockMvc
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 扩展了 .我们不会详细介绍 ,但总的来说,它包含我们所有页面的通用功能。
例如,如果我们的应用程序有一个导航栏、全局错误消息和其他
功能,我们可以将此 logic 放在共享位置。AbstractPage AbstractPage |
2 | 我们所在的 HTML 页面的每个部分都有一个成员变量
感兴趣。这些是 类型 。WebDriver 的 PageFactory 允许我们删除一个
来自的 HtmlUnit 版本的大量代码
每。PageFactory#initElements(WebDriver,Class<T>) 方法通过使用字段名称并查找它来自动解析每个
按 HTML 页面中元素的 or 来获取。WebElement CreateMessagePage WebElement WebElement id name |
3 | 我们可以使用 @FindBy 注解来覆盖默认的查找行为。我们的示例展示了如何使用注释来查找带有选择器 (input[type=submit]) 的提交按钮。@FindBy css |
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)
(2)
private lateinit var summary: WebElement
private lateinit var text: WebElement
(3)
@FindBy(css = "input[type=submit]")
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
this.summary.sendKeys(summary)
text.sendKeys(details)
submit.click()
return PageFactory.initElements(driver, resultPage)
}
companion object {
fun to(driver: WebDriver): CreateMessagePage {
driver.get("http://localhost:9990/mail/messages/form")
return PageFactory.initElements(driver, CreateMessagePage::class.java)
}
}
}
1 | CreateMessagePage 扩展了 .我们不会详细介绍 ,但总的来说,它包含我们所有页面的通用功能。
例如,如果我们的应用程序有一个导航栏、全局错误消息和其他
功能,我们可以将此 logic 放在共享位置。AbstractPage AbstractPage |
2 | 我们所在的 HTML 页面的每个部分都有一个成员变量
感兴趣。这些是 类型 。WebDriver 的 PageFactory 允许我们删除一个
来自的 HtmlUnit 版本的大量代码
每。PageFactory#initElements(WebDriver,Class<T>) 方法通过使用字段名称并查找它来自动解析每个
按 HTML 页面中元素的 or 来获取。WebElement CreateMessagePage WebElement WebElement id name |
3 | 我们可以使用 @FindBy 注解来覆盖默认的查找行为。我们的示例展示了如何使用注释来查找带有选择器 (input[type=submit]) 的提交按钮。@FindBy css |
最后,我们可以验证是否已成功创建新消息。以下内容 断言使用 AssertJ 断言库:
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
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
在到目前为止的示例中,我们以最简单的方式使用
可能,通过构建一个基于加载的
Spring TestContext 框架。此处重复此方法,如下所示:MockMvcHtmlUnitDriverBuilder
WebDriver
WebApplicationContext
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
这更详细,但是,通过构建 with 实例,我们有
MockMvc 的全部功能触手可及。WebDriver
MockMvc
有关创建实例的其他信息,请参阅设置选项。MockMvc |
MockMvc 和 Geb
在上一节中,我们了解了如何将 MockMvc 与 WebDriver 结合使用。在本节中,我们将 使用 Geb 使我们的测试更加 Groovy-er。
为什么选择 Geb 和 MockMvc?
Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 WebDriver 的然而,Geb 通过处理一些 样板代码。
MockMvc 和 Geb 设置
我们可以轻松地使用使用 MockMvc 的 Selenium WebDriver 初始化 Geb,因为
遵循:Browser
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
这是使用 .对于更高级的
用法,请参阅 Advanced MockMvcHtmlUnitDriverBuilder 。MockMvcHtmlUnitDriverBuilder |
这确保了作为服务器的任何 URL 引用都被定向到我们的实例,而不需要真正的 HTTP 连接。任何其他 URL 为
通过正常使用网络连接请求。这让我们可以轻松地测试
CDN 的localhost
MockMvc
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() }
}
}
我们的扩展 .我们不详细介绍 ,但是,在
总结,它包含我们所有页面的通用功能。我们定义了一个 URL,其中
这个页面可以找到。这样,我们就可以导航到该页面,如下所示:CreateMessagePage
Page
Page
to CreateMessagePage
我们还有一个 closure 来确定我们是否在指定的页面。它应该
如果我们在正确的页面上,则返回。这就是为什么我们可以断言我们在
正确的页面,如下所示:at
true
then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用 assertion,以便我们可以确定哪里出了问题 如果我们在错误的页面上。 |
接下来,我们创建一个闭包,指定
页。我们可以使用 jQuery 风格的 Navigator
API 来选择我们感兴趣的内容。content
最后,我们可以验证是否已成功创建新消息,如下所示:
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
有关如何充分利用 Geb 的更多详细信息,请参阅 The Book of Geb 用户手册。
3.6.3. 客户端 REST 测试
您可以使用客户端测试来测试内部使用 .这
这个想法是声明预期的请求并提供 “stub” 响应,以便您可以
专注于隔离测试代码 (即,不运行服务器) 。以下内容
示例展示了如何做到这一点:RestTemplate
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
// Test code that uses the above RestTemplate ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())
// Test code that uses the above RestTemplate ...
mockServer.verify()
在前面的示例中,(客户端 REST 的中心类
tests) 配置自定义
根据预期断言实际请求并返回 “stub” 响应。在这个
case 中,我们期望请求并希望返回带有 content 的 200 响应。我们可以将其他预期请求和存根响应定义为
需要。当我们定义预期的请求和存根响应时,可以是
像往常一样在客户端代码中使用。在测试结束时,可以
用于验证是否已满足所有期望。MockRestServiceServer
RestTemplate
ClientHttpRequestFactory
/greeting
text/plain
RestTemplate
mockServer.verify()
默认情况下,请求应按照声明 expectations 的顺序进行。你
可以在构建服务器时设置选项,在这种情况下,所有
检查 expectations 以查找给定请求的匹配项。这意味着
请求可以按任何顺序出现。以下示例使用 :ignoreExpectOrder
ignoreExpectOrder
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
即使默认情况下使用无序请求,每个请求也只允许运行一次。
该方法提供了一个重载的变体,该变体接受指定计数范围 (的参数,例如 、 、 、 、 等)。以下示例使用 :expect
ExpectedCount
once
manyTimes
max
min
between
times
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
// ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())
// ...
mockServer.verify()
请注意,when 未设置(默认值),因此,请求
应按声明顺序排列,则该顺序仅适用于任何
预期请求。例如,如果 “/something” 需要两次,后跟
“/somewhere”三次,那么在出现之前应该有一个对 “/something” 的请求
对 “/somewhere” 的请求,但是,除了后面的 “/something” 和 “/somewhere” 之外,
请求可能随时出现。ignoreExpectOrder
作为上述所有方法的替代方案,客户端测试支持还提供了一个实现,您可以将其配置为
将其绑定到实例。这允许使用实际的服务器端处理请求
logic 的 logic,但没有运行 server。以下示例显示了如何执行此操作:ClientHttpRequestFactory
RestTemplate
MockMvc
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// Test code that uses the above RestTemplate ...
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))
// Test code that uses the above RestTemplate ...
静态导入
与服务器端测试一样,用于客户端测试的 Fluent API 需要一些静态
进口。通过搜索 很容易找到这些 .Eclipse 用户应添加 和
“→→ Content Editor”下的 Eclipse 首选项中的 “favorite static members”
协助 → 收藏夹。这允许在键入
静态方法名称。其他 IDE(如 IntelliJ)可能不需要任何其他
配置。检查静态成员是否支持代码完成。MockRest*
MockRestRequestMatchers.*
MockRestResponseCreators.*
客户端 REST 测试的更多示例
Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。
3.7. WebTest客户端
WebTestClient
是围绕 WebClient 的一个薄壳,
使用它来执行请求,并公开一个专用的 Fluent API 来验证响应。 绑定到 WebFlux 应用程序,或者它可以测试任何
Web 服务器。WebTestClient
Kotlin 用户:请参阅此部分,了解如何使用 .WebTestClient |
3.7.1. 设置
要创建 ,您必须选择多个服务器设置选项之一。
实际上,您要么将 WebFlux 应用程序配置为绑定到,要么使用
用于连接到正在运行的服务器的 URL。WebTestClient
绑定到控制器
以下示例显示了如何创建服务器设置以一次测试一个:@Controller
client = WebTestClient.bindToController(new TestController()).build();
client = WebTestClient.bindToController(TestController()).build()
前面的示例加载 WebFlux Java 配置并注册给定的控制器。测试生成的 WebFlux 应用程序 使用 mock request 和 response 对象来使用 HTTP 服务器。还有更多方法 以自定义默认的 WebFlux Java 配置。
绑定到路由器功能
下面的示例展示了如何从 RouterFunction 设置服务器:
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()
在内部,配置将传递给 。
通过使用 mock 在没有 HTTP 服务器的情况下测试生成的 WebFlux 应用程序
request 和 response 对象。RouterFunctions.toWebHandler
绑定到ApplicationContext
以下示例显示了如何从 Spring 的 Spring 配置中设置服务器 application 或其某些子集:
@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 |
在内部,将配置传递给 以设置请求
处理链。请参阅 WebHandler API
更多细节。生成的 WebFlux 应用程序在没有 HTTP 服务器的情况下由
使用 Mock 请求和响应对象。WebHttpHandlerBuilder
绑定到服务器
以下服务器设置选项允许您连接到正在运行的服务器:
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
客户端生成器
除了前面描述的 server 设置选项之外,您还可以配置 client
选项,包括基本 URL、默认标头、客户端筛选器等。这些选项
在 .对于所有其他配置,您需要使用 从 server 过渡到 client 配置,如下所示:bindToServer
configureClient()
client = WebTestClient.bindToController(new TestController())
.configureClient()
.baseUrl("/test")
.build();
client = WebTestClient.bindToController(TestController())
.configureClient()
.baseUrl("/test")
.build()
3.7.2. 编写测试
WebTestClient
提供与 WebClient 相同的 API,直到使用 执行请求为止。接下来是一个链接的 API 工作流,用于验证响应。exchange()
exchange()
通常,您首先断言响应状态和标头,如下所示:
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
然后,指定如何解码和使用响应正文:
-
expectBody(Class<T>)
:解码为单个对象。 -
expectBodyList(Class<T>)
:解码并收集对象到 。List<T>
-
expectBody()
:解码为 JSON 内容或空正文。byte[]
然后,你可以对 body 使用内置断言。以下示例显示了一种执行此操作的方法:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).hasSize(3).contains(person);
import org.springframework.test.web.reactive.server.expectBodyList
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList<Person>().hasSize(3).contains(person)
您还可以超越内置断言并创建自己的断言,如下例所示:
import org.springframework.test.web.reactive.server.expectBody
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (e.g. AssertJ)...
});
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody<Person>()
.consumeWith {
// custom assertions (e.g. AssertJ)...
}
您还可以退出工作流程并获得结果,如下所示:
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
import org.springframework.test.web.reactive.server.expectBody
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk
.expectBody<Person>()
.returnResult()
当您需要使用泛型解码为目标类型时,请查找重载的方法
接受 ParameterizedTypeReference 而不是 .Class<T> |
无内容
如果响应没有内容(或者您不关心它是否有内容),请使用 ,这可确保
资源被释放。以下示例显示了如何执行此操作:Void.class
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound
.expectBody<Unit>()
或者,如果要断言没有响应内容,可以使用类似于下面的代码:
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
client.post().uri("/persons")
.bodyValue(person)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty()
JSON 内容
使用 时,响应将作为 .这适用于
原始内容断言。例如,您可以使用 JSONAssert 来验证 JSON 内容,如下所示:expectBody()
byte[]
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
您还可以使用 JSONPath 表达式,如下所示:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason")
流式响应
要测试无限流(例如,或 ),
您需要在响应状态后立即退出链式 API(使用 )
和标头断言,如下例所示:"text/event-stream"
"application/stream+json"
returnResult
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>()
现在,您可以在解码对象出现时使用 ,断言它们,然后
在满足测试目标的某个时间点取消。我们建议使用 from the module 来执行此操作,如下例所示:Flux<T>
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()
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 中编写的集成测试。