测试
测试是企业软件开发不可或缺的一部分。本章重点介绍 IoC 原则为单元测试添加的 value 以及 Spring Framework 对整合测试的支持的好处。 (对企业中的测试进行全面处理超出了本次参考的范围 manual.)
单元测试
与传统的 Java EE 开发相比,依赖注入应该使 code 对容器的依赖性降低。构成 application 的 POJO 应该在 JUnit 或 TestNG 测试中可测试,使用new
operator 实例化 object,不使用 Spring 或任何其他容器。您可以使用mock objects(与其他有价值的测试技术结合使用)来单独测试您的 code。如果您遵循 Spring 的 architecture 建议,那么代码库的干净分层和组件化便于单元测试。例如,您可以通过存根或 mocking DAO 或 repository 接口来测试服务层 objects,而无需在 running 单元测试时访问持久数据。
True 单元测试通常非常快速地运行,因为没有要设置的运行时基础结构。强调 true 单元测试作为开发方法的一部分可以提高您的工作效率。您可能不需要测试章节的这一部分来帮助您为 IoC-based applications 编写有效的单元测试。但是,对于某些单元测试场景,Spring Framework 提供了 mock objects 和测试支持 classes,本章对此进行了描述。
MockObjects
Spring 包含许多专用于 mocking 的包:
环境
org.springframework.mock.env
包包含Environment
和PropertySource
抽象的 mock implementations(参见Bean 定义 Profiles和PropertySource 抽象)。 MockEnvironment
和MockPropertySource
对于 code 开发 out-of-container 测试很有用,因为 code 取决于 environment-specific properties。
JNDI
org.springframework.mock.jndi
包中包含 JNDI SPI 的 实现,您可以使用它为测试套件或 stand-alone applications 设置简单的 JNDI 环境。例如,如果 JDBC DataSource
实例在 test code 中绑定到与 Java EE 容器中相同的 JNDI 名称,则可以在测试方案中重复使用 application code 和 configuration 而无需修改。
ServletAPI
org.springframework.mock.web
包中包含一组全面的 Servlet API mock objects,可用于测试 web 上下文,控制器和过滤器。这些 mock objects 的目标是使用 Spring 的 Web MVC framework,并且通常比动态 mock objects(例如EasyMock)或替代 Servlet API mock objects(例如MockObjects)更方便使用。
从 Spring Framework 5.0 开始,
org.springframework.mock.web
中的 mock objects 基于 Servlet 4.0 API。
Spring MVC Test framework 构建在 mock Servlet API objects 之上,为 Spring MVC 提供 integration 测试 framework。见Spring MVC Test Framework。
SpringWebReactive
org.springframework.mock.http.server.reactive
包中包含_m mplemplements of ServerHttpRequest
和ServerHttpResponse
,用于 WebFlux applications。 org.springframework.mock.web.server
包中包含 mock ServerWebExchange
,它取决于那些 mock 请求和响应 objects。
MockServerHttpRequest
和MockServerHttpResponse
都从与 server-specific __mplementations 相同的抽象 base classes 扩展,并与它们共享行为。对于 example,mock 请求一旦创建就是不可变的,但您可以使用ServerHttpRequest
中的mutate()
方法创建修改后的实例。
在 mock 响应的 order 中正确实现 write contract 和 return 一个写完成句柄(即Mono<Void>
),它默认使用Flux
和cache().then()
,它缓冲数据并使其可用于测试中的断言。 Applications 可以设置自定义写入 function(对于 example,以测试无限流)。
WebTestClient构建在 mock 请求和响应之上,以支持在没有 HTTP 服务器的情况下测试 WebFlux applications。 client 也可用于 running 服务器的 end-to-end 测试。
单元测试支持Classes
Spring 包含许多可以帮助进行单元测试的 classes。它们分为两类:
一般测试实用程序
org.springframework.test.util
包中包含几个用于单元和 integration 测试的通用实用程序。
ReflectionTestUtils
是 reflection-based 实用程序方法的集合。您可以在测试需要更改常量的 value,设置非public
字段,调用非public
setter 方法,或在测试 application code 时调用非public
configuration 或生命周期回调方法时使用这些方法用例如下:
- ORM 框架(例如 JPA 和 Hibernate)容忍
private
或protected
字段访问,而不是域实体中 properties 的public
setter 方法。 - Spring 支持 annotations(例如
@Autowired
,@Inject
和@Resource
),它们为private
或protected
字段,setter 方法和 configuration 方法提供依赖注入。 - 使用_an和
@PreDestroy
这样的 annotations 进行生命周期回调方法。
AopTestUtils是 AOP-related 实用程序方法的集合。您可以使用这些方法获取隐藏在一个或多个 Spring 代理后面的基础目标 object 的 reference。例如,如果您使用 library(例如 EasyMock 或 Mockito)将 bean 配置为动态 mock,并且 mock 包含在 Spring 代理中,则可能需要直接访问底层 mock 以配置对它的期望并执行验证。对于 Spring 的核心 AOP 实用程序,请参阅AopUtils和AopProxyUtils。
SpringMVC测试实用程序
org.springframework.test.web
包中包含ModelAndViewAssert,您可以将其与 JUnit,TestNG 或任何其他测试 framework 结合使用,以进行处理 Spring MVC ModelAndView
objects 的单元测试。
单元测试 Spring MVC 控制器 要将 Spring MVC
Controller
classes 作为 POJO 进行单元测试,请在 Spring 的Servlet API 模拟中使用ModelAndViewAssert
与MockHttpServletRequest
,MockHttpSession
等结合使用。要对 Spring MVC 和 RESTController
classes 与 Spring MVC 的WebApplicationContext
configuration 一起进行彻底的 integration 测试,请改用Spring MVC Test Framework。
集成测试
本节(本章的大部分内容)涵盖了 Spring applications 的 integration 测试。它包括以下主题:
概述
能够执行某些 integration 测试非常重要,无需部署到 application 服务器或连接到其他企业基础结构。这样做可以让您测试以下内容:
- Spring IoC 容器上下文的正确连线。
- 使用 JDBC 或 ORM 工具进行数据访问。这可以包括诸如 SQL statements,Hibernate 查询,JPA 实体映射等的正确性。
Spring Framework 为spring-test
模块中的 integration 测试提供 first-class 支持。实际的 JAR 文件的 name 可能包含 release version,也可能是 long org.springframework.test
形式,具体取决于你从哪里得到它(参见关于依赖管理的部分的解释)。这个 library 包含org.springframework.test
包,其中包含用于 Spring 容器的 integration 测试的有价值的 classes。此测试不依赖于 application 服务器或其他部署环境。这些测试比单元测试慢得多,但比依赖部署到 application 服务器的等效 Selenium 测试或 remote 测试要快得多。
在 Spring 2.5 及更高版本中,单元和 integration 测试支持以 annotation-driven Spring TestContext Framework的形式提供。 TestContext framework 与所使用的实际测试 framework 无关,它允许在各种环境中检测测试,包括 JUnit,TestNG 等。
集成测试的目标
Spring 的集成测试支持有以下主要目标:
- 在测试之间管理Spring IoC 容器缓存。
- 提供测试fixture实例的依赖注入。
- 提供transaction management适合整合测试。
- 提供Spring-specific base classes,帮助开发人员编写集成测试。
接下来的几节将介绍每个目标,并提供指向 implementation 和 configuration 详细信息的链接。
上下文管理和缓存
Spring TestContext Framework 提供 Spring ApplicationContext
实例和WebApplicationContext
实例的一致 loading 以及这些上下文的缓存。支持缓存加载的上下文非常重要,因为启动 time 可能会成为一个问题 - 不是因为 Spring 本身的开销,而是因为 Spring 容器实例化的 objects 需要 time 来实例化。例如,具有 50 到 100 个 Hibernate 映射 files 的项目可能需要 10 到 20 秒来加载映射 files,并且在每个测试夹具中运行每个测试之前产生该成本会导致整体测试运行速度变慢,从而降低开发人员的工作效率。
Test classes 通常声明 XML 的资源位置的 array 或 Groovy configuration 元数据 - 通常在 classpath 中 - 或者用于配置 application 的带注释的 class 的 array。这些位置或 classes 与web.xml
或 production 部署的其他 configuration files 中指定的位置或 classes 相同或相似。
默认情况下,一旦加载,配置的ApplicationContext
将被重用于每个测试。因此,每个测试套件仅产生一次设置成本,并且后续测试执行要快得多。在此 context 中,术语“测试套件”表示在同一 JVM 中运行所有测试 - 例如,对于给定项目或模块,所有测试都来自 Ant,Maven 或 Gradle build。在不太可能的情况下,测试会破坏 application context 并需要重新加载(例如,通过修改 bean 定义或 application object 的 state),TestContext framework 可以配置为重新加载 configuration 并重建 application context,然后再执行下一个测试。
请参阅带有 TestContext framework 的Context Management和Context 缓存。
测试fixture实例的依赖注入
当 TestContext framework 加载 application context 时,它可以选择使用依赖注入来配置 test classes 的实例。这提供了一种方便的机制,通过使用 application context 中的预配置 beans 来设置测试装置。这里的一个强大好处是您可以在各种测试场景中重用 application 上下文(例如,用于配置 Spring-managed object 图,transactional proxies,DataSource
实例等),从而避免了为单个测试用例复制复杂测试夹具设置的需要。
作为一个例子,考虑一个我们有一个 class(HibernateTitleRepository
)实现Title
域实体的数据访问逻辑的场景。我们想编写测试以下方面的 integration 测试:
- Spring configuration:基本上,与
HibernateTitleRepository
bean 的 configuration 相关的所有内容是否正确并且存在? - Hibernate 映射文件 configuration:是否所有映射都正确并且是否正确设置了 lazy-loading 设置?
HibernateTitleRepository
的逻辑:此 class 的已配置实例是否按预期执行?
请参阅使用TestContext framework依赖注入测试夹具。
TransactionManagement
访问真实数据库的测试中的一个常见问题是它们对持久性 store 的 state 的影响。即使您使用开发数据库,对 state 的更改也可能会影响将来的测试。此外,许多操作(例如插入或修改持久数据)无法在 transaction 之外执行(或验证)。
TestContext framework 解决了这个问题。默认情况下,framework 为每个测试创建并回滚 transaction。您可以编写可以假设存在 transaction 的 code。如果在测试中调用事务代理的 objects,则根据其配置的 transactional 语义,它们的行为正确。此外,如果测试方法在为测试管理的 transaction 中运行时删除所选表的内容,则 transaction 默认回滚,并且数据库在执行测试之前返回其 state。通过使用测试的 application context 中定义的PlatformTransactionManager
bean 为测试提供 Transactional 支持。
如果您希望 transaction 提交(异常,但在您希望特定测试填充或修改数据库时偶尔有用),您可以通过使用@Commit annotation 告诉 TestContext framework 使 transaction 提交而不是回滚。
请参阅TestContext framework的transaction management。
集成测试的支持类
Spring TestContext Framework 提供了几个abstract
支持 classes,简化了 integration 测试的编写。这些基本测试 classes 为测试 framework 提供了 well-defined 钩子以及方便的实例变量和方法,使您可以访问:
ApplicationContext
,用于执行显式 bean 查找或测试 context 的 state 作为整体。- 一个
JdbcTemplate
,用于执行 SQL statements 来查询数据库。您可以在执行 database-related application code 之前和之后使用此类查询来确认数据库 state,并且 Spring 确保此类查询在与 application code 相同的 transaction 范围内运行。与 ORM 工具结合使用时,请务必避免使用误报。
此外,您可能希望使用特定于项目的实例变量和方法创建自己的自定义 application-wide 超类。
请参阅TestContext framework的支持 classes。
JDBC测试支持
org.springframework.test.jdbc
包中包含JdbcTestUtils
,它是 JDBC-related 实用程序函数的集合,旨在简化标准数据库测试方案。具体来说,JdbcTestUtils
提供以下静态实用程序方法。
countRowsInTable(..)
:计算给定 table 中的行数。countRowsInTableWhere(..)
:使用提供的WHERE
子句计算给定 table 中的行数。deleteFromTables(..)
:删除指定表中的所有行。deleteFromTableWhere(..)
:使用提供的WHERE
子句删除给定 table 中的行。dropTables(..)
:删除指定的表。
AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests提供了方便的方法,委托
JdbcTestUtils
中的上述方法。
spring-jdbc
模块支持配置和启动嵌入式数据库,您可以在与数据库交互的 integration 测试中使用它。有关详细信息,请参阅嵌入式数据库支持和使用嵌入式数据库测试数据访问逻辑。
Annotations
本节介绍了在测试 Spring applications 时可以使用的注释。它包括以下主题:
- Spring Testing Annotations
- 标准 Annotation 支持
- Spring JUnit 4 测试注释
- Spring JUnit Jupiter Testing Annotations
- Meta-Annotation 支持测试
SpringTestingAnnotations
Spring Framework 提供了以下一组 Spring-specific注解,您可以在单元和集成测试中结合 TestContext framework 使用它们。有关详细信息,请参阅相应的 javadoc,包括默认属性值,属性别名和其他详细信息。
Spring 的测试注解包括以下内容:
- @BootstrapWith
- @ContextConfiguration
- @WebAppConfiguration
- @ContextHierarchy
- @ActiveProfiles
- @TestPropertySource
- @DirtiesContext
- @TestExecutionListeners
- @Commit
- @Rollback
- @BeforeTransaction
- @AfterTransaction
- @Sql
- @SqlConfig
- @SqlGroup
BootstrapWith注解
@BootstrapWith
是一个 class-level 注释,可用于配置 Spring TestContext Framework 的引导方式。具体来说,您可以使用@BootstrapWith
指定自定义TestContextBootstrapper
。有关详细信息,请参阅引导 TestContext framework部分。
ContextConfiguration注解
@ContextConfiguration
定义 class-level 元数据,用于确定如何加载和配置ApplicationContext
for integration 测试。具体来说,@ContextConfiguration
声明 application context 资源locations
或用于加载 context 的带注释的classes
。
资源位置通常是位于 classpath 中的 XML configuration files 或 Groovy 脚本,而带注释的 classes 通常是@Configuration
classes。但是,资源位置也可以引用文件系统中的 files 和脚本,带注释的 classes 可以是 component classes,依此类推。
以下 example 显示引用 XML 文件的@ContextConfiguration
annotation:
1 | 1) ( |
1 | 参考 XML 文件。 |
以下 example 显示引用 class 的@ContextConfiguration
annotation:
1 | 1) ( |
1 | 参考 class。 |
作为声明资源位置或带注释的 classes 的替代或补充,您可以使用@ContextConfiguration
来声明ApplicationContextInitializer
classes。以下 example 显示了这样的情况:
1 | 1) ( |
1 | 声明初始化程序 class。 |
您也可以选择使用@ContextConfiguration
来声明ContextLoader
策略。但请注意,您通常不需要显式配置加载器,因为默认加载器支持initializers
和资源locations
或带注释的classes
。
以下 example 使用位置和加载器:
1 | 1) ( |
1 | 配置位置和自定义加载程序。 |
@ContextConfiguration
支持继承资源位置或 configuration classes 以及超类声明的 context 初始值设定项。
有关详细信息,请参阅Context Management和@ContextConfiguration
javadocs。
WebAppConfiguration注解
@WebAppConfiguration
是一个 class-level annotation,可以用来声明为 integration 测试加载的ApplicationContext
应该是WebApplicationContext
。仅仅在测试 class 上存在@WebAppConfiguration
就可以确保为测试加载WebApplicationContext
,使用"file:src/main/webapp"
的默认 value 作为 web application 根目录的路径(即资源库路径)。在幕后使用资源基本路径来创建MockServletContext
,其用作测试WebApplicationContext
的ServletContext
。
以下 example 显示了如何使用@WebAppConfiguration
annotation:
1 |
|
1 | @WebAppConfiguration annotation。 |
要覆盖缺省值,可以使用隐式value
属性指定不同的基本资源路径。支持classpath:
和file:
资源前缀。如果未提供资源前缀,则假定该路径是文件系统资源。以下 example 显示了如何指定 classpath 资源:
1 |
|
1 | 指定 classpath 资源。 |
请注意,@WebAppConfiguration
必须与@ContextConfiguration
结合使用,可以在单个测试 class 中,也可以在 test class 层次结构中使用。有关更多详细信息,请参阅@WebAppConfiguration javadoc。
ContextHierarchy注解
@ContextHierarchy
是一个 class-level annotation,用于为 integration 测试定义ApplicationContext
实例的层次结构。 @ContextHierarchy
应该使用一个或多个@ContextConfiguration
实例的列表声明,每个实例在 context 层次结构中定义 level。以下示例演示了在单个测试 class 中使用@ContextHierarchy
(@ContextHierarchy
也可以在测试 class 层次结构中使用):
1 |
|
如果需要合并或覆盖 test class 层次结构中 context 层次结构的给定 level 的 configuration,则必须通过向 class 层次结构中每个对应 level 的@ContextConfiguration
中的name
属性提供相同的 value 来显式 name 该 level。有关更多示例,请参阅Context Hierarchies和@ContextHierarchy javadoc。
ActiveProfiles注解
@ActiveProfiles
是一个 class-level annotation,用于声明
以下 example 表明dev
profile 应该是 active:
1 |
|
1 | 指示dev profile 应为 active。 |
以下 example 表示dev
和integration
profiles 都应该是 active:
1 |
|
1 | 指示dev 和integration profiles 应为 active。 |
@ActiveProfiles
默认提供对继承超类声明的 active bean 定义 profiles 的支持。您还可以通过实现自定义ActiveProfilesResolver并使用@ActiveProfiles
的resolver
属性进行注册来以编程方式解析 active bean definition profiles。
有关示例和更多详细信息,请参阅Context Configuration with Environment Profiles和@ActiveProfiles javadoc。
TestPropertySource注解
@TestPropertySource
是一个 class-level 注释,您可以使用它来配置 properties files 和内联 properties 的位置,以便将Environment
添加到Environment
的PropertySources
中,以便为 integration 测试加载ApplicationContext
。
Test property 源的优先级高于从操作系统环境或 Java 系统 properties 加载的源以及 application 以声明方式通过@PropertySource
或以编程方式添加的 property 源。因此,test property 源可用于有选择地覆盖 system 和 application property 源中定义的 properties。此外,内联 properties 的优先级高于从资源位置加载的 properties。
以下 example 演示了如何从 classpath 声明 properties 文件:
1 |
|
1 | 从 classpath 的根目录中的test.properties 获取 properties。 |
以下 example 演示了如何声明内联 properties:
1 |
|
1 | 声明timezone 和port properties。 |
DirtiesContext注解
@DirtiesContext
表示在执行测试期间底层 Spring ApplicationContext
已被弄脏(即,测试以某种方式修改或损坏它 - 例如,通过更改 singleton bean 的 state)并且应该关闭。当 application context 被标记为脏时,它将从测试 framework 的缓存中删除并关闭。因此,对于需要具有相同 configuration 元数据的 context 的任何后续测试,都会重建基础 Spring 容器。
您可以在同一 class 或 class 层次结构中使用@DirtiesContext
作为 class-level 和 method-level annotation。在这种情况下,ApplicationContext
在任何此类带注释的方法之前或之后以及当前测试 class 之前或之后被标记为脏,具体取决于配置的methodMode
和classMode
。
以下示例说明 context 何时会因各种 configuration 方案而变脏:
- 在当前测试 class 之前,当 class 上的 class 模式设置为
BEFORE_CLASS
时声明。
1 | 1) ( |
1 | 在当前测试 class 之前弄脏了 context。 |
- 在当前测试 class 之后,在 class 上声明 class 模式时设置为
AFTER_CLASS
(i.e.,默认为 class 模式)。
1 | 1) ( |
1 | 在当前测试 class 之后弄脏了 context。 |
- 在当前测试 class 中的每个测试方法之前,当 class 上的 class 模式设置为
BEFORE_EACH_TEST_METHOD.
时声明
1 | 1) ( |
1 | 在每个测试方法之前弄脏 context。 |
- 在当前测试 class 中的每个测试方法之后,当 class 上的 class 模式设置为
AFTER_EACH_TEST_METHOD.
时声明
1 | 1) ( |
1 | 在每个测试方法之后弄脏 context。 |
- 在当前测试之前,在方法模式设置为
BEFORE_METHOD
的方法上声明。
1 | 1) ( |
1 | 在当前测试方法之前弄脏 context。 |
- 在当前测试之后,在方法模式设置为
AFTER_METHOD
(i.e.,默认方法模式)的方法上声明。
1 | 1) ( |
1 | 在当前测试方法之后弄脏 context。 |
如果在 context 配置为带@ContextHierarchy
的 context 层次结构的一部分的测试中使用@DirtiesContext
,则可以使用hierarchyMode
flag 来控制 context 缓存的清除方式。默认情况下,使用详尽的算法来清除 context 缓存,不仅包括当前 level,还包括与当前测试共享祖先 context common 的所有其他 context 层次结构。驻留在 common 祖先 context 的 sub-hierarchy 中的所有ApplicationContext
实例将从 context 缓存中删除并关闭。如果穷举算法对于特定用例而言过度,则可以指定更简单的当前 level 算法,如下面的 example 所示。
1 |
|
1 | 使用 current-level 算法。 |
有关EXHAUSTIVE
和CURRENT_LEVEL
算法的更多详细信息,请参阅DirtiesContext.HierarchyMode javadoc。
TestExecutionListeners注解
@TestExecutionListeners
定义了 class-level 元数据,用于配置应该使用TestContextManager
注册的TestExecutionListener
__mplement。通常,@TestExecutionListeners
与@ContextConfiguration
一起使用。
以下 example 显示了如何注册两个TestExecutionListener
implementations:
1 |
|
1 | 注册两个TestExecutionListener implementations。 |
默认情况下,@TestExecutionListeners
支持继承的 listeners。有关 example 和更多详细信息,请参阅javadoc。
Commit注解
@Commit
表示应在测试方法完成后提交 transactional 测试方法的 transaction。您可以使用@Commit
作为@Rollback(false)
的直接替换来更明确地传达 code 的意图。类似于@Rollback
,@Commit
也可以声明为 class-level 或 method-level 注释。
以下 example 显示了如何使用@Commit
annotation:
1 | 1) ( |
1 | 将测试结果提交给数据库。 |
Rollback注解
@Rollback
表示在测试方法完成后是否应回滚 transactional 测试方法的 transaction。如果是true
,transaction 将被回滚。否则,提交 transaction(另请参见@Commit)。即使没有显式声明@Rollback
,Spring TestContext Framework 中的 integration 测试的回滚默认为true
。
声明为 class-level annotation 时,@Rollback
定义 test class 层次结构中所有测试方法的默认回滚语义。当声明为 method-level annotation 时,@Rollback
定义特定测试方法的回滚语义,可能会覆盖 class-level @Rollback
或@Commit
语义。
以下 example 导致测试方法的结果不被回滚(即,结果被提交到数据库):
1 | 1) ( |
1 | 不要回滚结果。 |
BeforeTransaction注解
@BeforeTransaction
表示在 transaction 启动之前_11方法应该是 run,对于已经使用 Spring 的@Transactional
annotation 在 transaction 中配置为 run 的测试方法。从 Spring Framework 4.3 开始,@BeforeTransaction
方法不需要public
,可以在 Java 8-based 接口默认方法中声明。
以下 example 显示了如何使用@BeforeTransaction
annotation:
1 | 1) ( |
1 | 在 transaction 之前运行此方法。 |
AfterTransaction注解
@AfterTransaction
表示在 transaction 结束后,对于已在 transaction 中使用 Spring 的@Transactional
annotation 配置为 run 的测试方法,应该 run 注释void
方法。从 Spring Framework 4.3 开始,@AfterTransaction
方法不需要public
,可以在 Java 8-based 接口默认方法中声明。
1 | 1) ( |
1 | 在 transaction 之后运行此方法。 |
Sql注解
@Sql
用于注释测试 class 或测试方法,以便在 integration 测试期间将 SQL 脚本配置为针对给定数据库运行。以下 example 显示了如何使用它:
1 |
|
1 | 运行此测试的两个脚本。 |
有关详细信息,请参阅使用 @Sql 以声明方式执行 SQL 脚本。
SqlConfig注解
@SqlConfig
定义元数据,用于确定如何解析和运行使用@Sql
annotation 配置的 SQL 脚本。以下 example 显示了如何使用它:
1 |
|
1 | 在 SQL 脚本中设置 comment 前缀和分隔符。 |
SqlGroup注解
@SqlGroup
是一个容器 annotation,它聚合了几个@Sql
注释。您可以使用@SqlGroup
本机声明几个嵌套的@Sql
注释,或者可以将它与 Java 8 对可重复注释的支持结合使用,其中@Sql
可以在同一个 class 或方法上多次声明,隐式生成此容器 annotation。以下 example 显示了如何声明 SQL group:
1 |
|
1 | 声明一组 SQL 脚本。 |
标准注解支持
对于 Spring TestContext Framework 的所有配置,标准语义支持以下注释。请注意,这些注释并非特定于测试,可以在 Spring Framework 中的任何位置使用。
@Autowired
@Qualifier
- 如果 JSR-250 存在,
@Resource
(javax.annotation) - 如果 JSR-250 存在,
@ManagedBean
(javax.annotation) - 如果 JSR-330 存在,
@Inject
(javax.inject) - 如果 JSR-330 存在,
@Named
(javax.inject) - 如果存在 JPA,
@PersistenceContext
(javax.persistence) - 如果存在 JPA,
@PersistenceUnit
(javax.persistence) @Required
@Transactional
JSR-250 生命周期注释
在 Spring TestContext Framework 中,您可以在ApplicationContext
中配置的任何 application 组件上使用@PostConstruct
和@PreDestroy
标准语义。但是,这些生命周期注释在实际测试 class 中的使用受到限制。
如果 test class 中的方法使用@PostConstruct
注释,则该方法在基础测试 framework 的任何 before 方法之前运行(对于 example,使用 JUnit Jupiter 的@BeforeEach
注释的方法),并且这适用于 test class 中的每个测试方法。另一方面,如果测试 class 中的方法使用@PreDestroy
注释,则该方法永远不会运行。因此,在 test class 中,我们建议您使用基础测试 framework 而不是@PostConstruct
和@PreDestroy
的测试生命周期回调。
SpringJUnit4测试注解
仅当与SpringRunner,Spring 的 JUnit 4 规则或Spring 的 JUnit 4 支持 classes一起使用时,才支持以下注解:
IfProfileValue注解
@IfProfileValue
表示为特定测试环境启用了带注释的测试。如果配置的ProfileValueSource
为提供的name
返回匹配的value
,则启用测试。否则,测试被禁用,并且有效地被忽略。
您可以在 class level,方法 level 或两者中应用@IfProfileValue
。对于 class 或其子类中的任何方法,@IfProfileValue
的使用优先于 method-level 用法。具体来说,如果在 class level 和 level 方法中都启用了测试,则启用测试。缺少@IfProfileValue
意味着隐式启用了测试。这类似于 JUnit 4 的@Ignore
annotation 的语义,除了@Ignore
的存在总是禁用测试。
以下 example 显示了具有@IfProfileValue
annotation 的测试:
1 | 1) ( |
1 | 仅在 Java 供应商为“Oracle Corporation”时运行此测试。 |
或者,您可以使用values
(带OR
语义)列表配置@IfProfileValue
,以便在 JUnit 4 环境中实现对测试组的 TestNG-like 支持。考虑以下 example:
1 | 1) ( |
1 | 运行此测试以进行单元测试和 integration 测试。 |
ProfileValueSourceConfiguration注解
@ProfileValueSourceConfiguration
是一个 class-level annotation,它指定在检索通过@IfProfileValue
annotation 配置的 profile 值时要使用的ProfileValueSource
类型。如果未为测试声明@ProfileValueSourceConfiguration
,则默认使用SystemProfileValueSource
。以下 example 显示了如何使用@ProfileValueSourceConfiguration
:
1 | 1) ( |
1 | 使用自定义 profile value 源。 |
Timed注解
@Timed
表示带注释的测试方法必须在指定的 time 时间段内(以毫秒为单位)完成执行。如果文本执行 time 超过指定的 time 时间段,则测试失败。
time 时间段包括运行测试方法本身,测试的任何重复(参见@Repeat
),以及测试夹具的任何设置或拆除。以下 example 显示了如何使用它:
1 | 1) ( |
1 | 将测试的 time 周期设置为一秒。 |
Spring 的@Timed
annotation 具有与 JUnit 4 的@Test(timeout=…)
支持不同的语义。具体来说,由于 JUnit 4 处理测试执行超时的方式(即,通过在单独的Thread
中执行测试方法),如果测试花费太长,则@Test(timeout=…)
抢先未通过测试。另一方面,Spring 的@Timed
并没有先发制人地失败,而是在失败之前等待测试完成。
Repeat注解
@Repeat
表示注释的测试方法必须重复 run。在 annotation 中指定执行测试方法的次数。
要重复的执行范围包括执行测试方法本身以及测试夹具的任何设置或拆除。以下 example 显示了如何使用@Repeat
annotation:
1 | 1) ( |
1 | 重复此测试十次。 |
SpringJUnitJupiterTestingAnnotations
仅当与SpringExtension和 JUnit Jupiter(即 JUnit 5 中的编程 model)一起使用时,才支持以下注释:
SpringJUnitConfig注解
@SpringJUnitConfig
是一个组合的 annotation,它将 JUnit Jupiter 中的@ExtendWith(SpringExtension.class)
与 Spring TestContext Framework 中的@ContextConfiguration
组合在一起。它可以在 class level 中用作@ContextConfiguration
的 drop-in 替换。关于 configuration 选项,@ContextConfiguration
和@SpringJUnitConfig
之间的唯一区别是可以使用@SpringJUnitConfig
中的value
属性声明带注释的 classes。
以下 example 显示了如何使用@SpringJUnitConfig
annotation 指定 configuration class:
1 | 1) ( |
1 | 指定 configuration class。 |
以下 example 显示了如何使用@SpringJUnitConfig
annotation 指定 configuration 文件的位置:
1 | 1) ( |
1 | 指定 configuration 文件的位置。 |
有关详细信息,请参阅Context Management以及@SpringJUnitConfig和@ContextConfiguration
的 javadoc。
SpringJUnitWebConfig注解
@SpringJUnitWebConfig
是一个组合注释,它将 JUnit Jupiter 中的@ExtendWith(SpringExtension.class)
与来自 Spring TestContext Framework 的@ContextConfiguration
和@WebAppConfiguration
组合在一起。您可以在 class level 中将其用作@ContextConfiguration
和@WebAppConfiguration
的 drop-in 替换。关于 configuration 选项,@ContextConfiguration
和@SpringJUnitWebConfig
之间的唯一区别是您可以使用@SpringJUnitWebConfig
中的value
属性声明带注释的 classes bu。此外,只能使用@SpringJUnitWebConfig
中的resourcePath
属性覆盖value
中的value
属性。
以下 example 显示了如何使用@SpringJUnitWebConfig
annotation 指定 configuration class:
1 | 1) ( |
1 | 指定 configuration class。 |
以下 example 显示了如何使用@SpringJUnitWebConfig
annotation 指定 configuration 文件的位置:
1 | 1) ( |
1 | 指定 configuration 文件的位置。 |
有关详细信息,请参阅Context Management以及@SpringJUnitWebConfig,@ContextConfiguration和@WebAppConfiguration的 javadoc。
EnabledIf注解
@EnabledIf
用于表示已注释的 JUnit Jupiter 测试 class 或测试方法已启用,如果提供的expression
求值为true
,则应为 run。具体来说,如果表达式求值为Boolean.TRUE
或String
等于true
(忽略大小写),则启用测试。当应用于 class level 时,默认情况下也会自动启用该 class 中的所有测试方法。
表达式可以是以下任何一种:
- Spring 表达语言(SpEL)表达式。对于 example:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
- Spring 环境中可用 property 的占位符。对于 example:
@EnabledIf("${smoke.tests.enabled}")
- 文字文字。对于 example:
@EnabledIf("true")
但请注意,不是 property 占位符动态解析结果的文本文字是零实用 value,因为@EnabledIf("false")
等同于@Disabled
,@EnabledIf("true")
在逻辑上没有意义。
您可以使用@EnabledIf
作为 meta-annotation 来创建自定义组合注释。对于 example,您可以创建自定义@EnabledOnMac
annotation,如下所示:
1 |
|
DisabledIf注解
@DisabledIf
用于表示已禁用带注释的 JUnit Jupiter 测试 class 或测试方法,如果提供的expression
求值为true
,则不应执行。具体来说,如果表达式求值为Boolean.TRUE
或String
等于true
(忽略大小写),则禁用测试。当应用于 class level 时,该 class 中的所有测试方法也会自动禁用。
表达式可以是以下任何一种:
- Spring 表达语言(SpEL)表达式。对于 example:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
- Spring 环境中可用 property 的占位符。对于 example:
@DisabledIf("${smoke.tests.disabled}")
- 文字文字。对于 example:
@DisabledIf("true")
但请注意,不是 property 占位符动态解析结果的文本文字是零实用 value,因为@DisabledIf("true")
等同于@Disabled
,@DisabledIf("false")
在逻辑上没有意义。
您可以使用@DisabledIf
作为 meta-annotation 来创建自定义组合注释。对于 example,您可以创建自定义@DisabledOnMac
annotation,如下所示:
1 |
|
Meta-Annotation支持测试
您可以使用大多数 test-related annotations 作为meta-annotations来创建自定义组合注释并减少测试套件中的配置重复。
您可以将以下各项与Spring TestContext Framework一起用作 meta-annotation。
@BootstrapWith
@ContextConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DirtiesContext
@WebAppConfiguration
@TestExecutionListeners
@Transactional
@BeforeTransaction
@AfterTransaction
@Commit
@Rollback
@Sql
@SqlConfig
@SqlGroup
@Repeat
(仅在 JUnit 4 上受支持)@Timed
(仅在 JUnit 4 上受支持)@IfProfileValue
(仅在 JUnit 4 上受支持)@ProfileValueSourceConfiguration
(仅在 JUnit 4 上受支持)@SpringJUnitConfig
(仅在 JUnit Jupiter 上受支持)@SpringJUnitWebConfig
(仅在 JUnit Jupiter 上受支持)@EnabledIf
(仅在 JUnit Jupiter 上受支持)@DisabledIf
(仅在 JUnit Jupiter 上受支持)
考虑以下 example:
1 |
|
如果我们发现我们在 JUnit 4-based 测试套件中重复了前面的 configuration,我们可以通过引入一个自定义组合 annotation 来集中 Spring 的 common test configuration 来减少重复,如下所示:
1 |
|
然后我们可以使用我们的自定义@TransactionalDevTestConfig
annotation 来简化基于 JUnit 4 的各个 test classes 的 configuration 配置,如下所示:
1 |
|
如果我们编写使用 JUnit Jupiter 的测试,我们可以进一步减少 code 重复,因为 JUnit 5 中的 annotations 也可以用作 meta-annotations。考虑以下 example:
1 |
|
如果我们发现我们在 JUnit Jupiter-based 测试套件中重复了前面的 configuration,我们可以通过引入一个自定义组合的 annotation 来集中 Spring 和 JUnit Jupiter 的 common test configuration 来减少重复,如下所示:
1 |
|
然后我们可以使用我们的自定义@TransactionalDevTestConfig
annotation 来简化基于 JUnit Jupiter 的各个 test classes 的 configuration 配置,如下所示:
1 |
|
由于 JUnit Jupiter 支持使用@Test
,@RepeatedTest
,ParameterizedTest
和其他作为 meta-annotations,因此您还可以在测试方法 level 中创建自定义组合注释。例如,如果我们希望创建一个组合的 annotation,它将来自 JUnit Jupiter 的@Test
和@Tag
注释与来自 Spring 的@Transactional
annotation 结合起来,我们可以创建@TransactionalIntegrationTest
annotation,如下所示:
1 |
|
然后我们可以使用我们的自定义@TransactionalIntegrationTest
annotation 来简化基于 JUnit Jupiter 的测试方法的配置,如下所示:
1 |
|
有关更多详细信息,请参阅Spring Annotation Programming Model wiki 页面。
SpringTestContext框架
Spring TestContext Framework(位于org.springframework.test.context
包中)提供了通用的 annotation-driven 单元和 integration 测试支持,它与正在使用的测试 framework 无关。 TestContext framework 在 configuration 上的约定上也非常重要,你可以通过 annotation-based configuration 覆盖合理的默认值。
除了通用测试基础结构之外,TestContext framework 还为 JUnit 4,JUnit Jupiter(AKA JUnit 5)和 TestNG 提供了显式支持。对于 JUnit 4 和 TestNG,Spring 提供abstract
支持 classes。此外,Spring 为 JUnit 4 提供了一个自定义 JUnit Runner
和自定义 JUnit Rules
,为 JUnit Jupiter 提供了一个自定义Extension
,可以让你编写 so-called POJO test classes。 POJO test classes 不需要扩展特定的 class 层次结构,例如abstract
support classes。
以下部分概述了 TestContext framework 的内部结构。如果您只对使用 framework 感兴趣并且不想使用自己的自定义 listeners 或自定义加载器扩展它,请随意直接转到 configuration(context management,依赖注入,transaction management),支持 classes和annotation 支持部分。
Key抽象
framework 的核心由TestContextManager
class 和TestContext
,TestExecutionListener
和SmartContextLoader
接口组成。为每个测试 class 创建TestContextManager
(对于 example,用于执行 JUnit Jupiter 中单个测试 class 中的所有测试方法)。反过来,TestContextManager
管理一个TestContext
,它保存当前测试的 context。 TestContextManager
还会在测试进行时更新TestContext
的 state,并委托TestExecutionListener
implementations,它通过提供依赖注入,管理 transactions 等来检测实际的测试执行情况。 SmartContextLoader
负责为给定的测试 class 加载ApplicationContext
。有关各种 implementations 的更多信息和示例,请参阅javadoc和 Spring 测试套件。
TestContext
TestContext
封装了执行测试的 context(与使用中的实际测试 framework 无关),并为其负责的测试实例提供 context management 和缓存支持。如果请求,TestContext
还委托SmartContextLoader
加载ApplicationContext
。
TestContextManager
TestContextManager
是 Spring TestContext Framework 的主要入口点,负责在 well-defined 测试执行点管理每个注册TestExecutionListener
的单个TestContext
和信令 events:
- 在特定测试 framework 之前的任何“before class”或“before all”方法之前。
- 测试实例 post-processing。
- 在特定测试 framework 之前的任何“之前”或“之前”方法之前。
- 在执行测试方法之前,但在测试设置之后。
- 在执行测试方法之后但在测试之前立即拆除。
- 在特定测试 framework 之后的任何“之后”或“之后”方法之后。
- 经过特定测试 framework 的任何“after class”或“after all”方法之后。
TestExecutionListener
TestExecutionListener
定义 API 以响应_l发布的TestContextManager
发布的 test-execution events。见TestExecutionListener Configuration。
Context加载器
ContextLoader
是 Spring 2.5 中引入的策略接口,用于为 Spring TestContext Framework 管理的 integration 测试加载ApplicationContext
。您应该实现SmartContextLoader
而不是此接口,以提供对带注释的 classes,active bean definition profiles,test property sources,context 层次结构和WebApplicationContext
支持的支持。
SmartContextLoader
是 Spring 3.1 中引入的ContextLoader
接口的扩展。 SmartContextLoader
SPI 取代 Spring 2.5 中引入的ContextLoader
SPI。具体来说,SmartContextLoader
可以选择 process 资源位置,带注释的 classes 或 context 初始值设定项。此外,SmartContextLoader
可以在其加载的 context 中设置 active bean definition profiles 和 test property sources。
Spring 提供以下 implementations:
DelegatingSmartContextLoader
:两个默认加载器之一,它内部委托给AnnotationConfigContextLoader
,GenericXmlContextLoader
或GenericGroovyXmlContextLoader
,这取决于为 test class 声明的 configuration 或默认位置或默认 configuration classes 的存在。仅当 Groovy 位于 classpath 时才启用 Groovy 支持。WebDelegatingSmartContextLoader
:两个默认加载器之一,它内部委托给AnnotationConfigWebContextLoader
,GenericXmlWebContextLoader
或GenericGroovyXmlWebContextLoader
,这取决于为 test class 声明的 configuration 或默认位置或默认 configuration classes 的存在。仅当 Class 上存在@WebAppConfiguration
时才使用 webContextLoader
。仅当 Groovy 位于 classpath 时才启用 Groovy 支持。AnnotationConfigContextLoader
:从带注释的 classes 中加载标准ApplicationContext
。AnnotationConfigWebContextLoader
:从带注释的 classes 中加载WebApplicationContext
。GenericGroovyXmlContextLoader
:从 Groovy 脚本或 XML configuration files 的资源位置加载标准ApplicationContext
。GenericGroovyXmlWebContextLoader
:从 Groovy 脚本或 XML configuration files 的资源位置加载WebApplicationContext
。GenericXmlContextLoader
:从 XML 资源位置加载标准ApplicationContext
。GenericXmlWebContextLoader
:从 XML 资源位置加载WebApplicationContext
。GenericPropertiesContextLoader
:从 Java properties files 加载标准ApplicationContext
。
引导TestContextFramework
Spring TestContext Framework 内部的默认 configuration 足以用于所有 common 用例。但是,有时开发团队或第三方 framework 要更改默认ContextLoader
,实现自定义TestContext
或ContextCache
,增加ContextCustomizerFactory
和TestExecutionListener
实现的默认 sets,依此类推。对于 TestContext framework 如何操作的 low-level 控制,Spring 提供了一个自举策略。
TestContextBootstrapper
定义 SPI 以引导 TestContext framework。 TestContextManager
使用TestContextBootstrapper
来加载当前测试的TestExecutionListener
实现并build它管理的TestContext
。您可以使用@BootstrapWith
直接或作为 meta-annotation 为 test class(或 test class 层次结构)配置自定义引导策略。如果未使用@BootstrapWith
显式配置引导程序,则使用DefaultTestContextBootstrapper
或WebTestContextBootstrapper
,具体取决于是否存在@WebAppConfiguration
。
由于TestContextBootstrapper
SPI 将来可能会发生变化(以适应新的要求),因此我们强烈建议实施者不要直接实现此接口,而应扩展AbstractTestContextBootstrapper
或其中一个具体的子类。
TestExecutionListener配置
Spring 提供以下默认注册的TestExecutionListener
__mplementations,完全在以下 order 中:
ServletTestExecutionListener
:为WebApplicationContext
配置 Servlet API 模拟。DirtiesContextBeforeModesTestExecutionListener
:处理“之前”模式的@DirtiesContext
注释。DependencyInjectionTestExecutionListener
:为测试实例提供依赖注入。DirtiesContextTestExecutionListener
:处理“后”模式的@DirtiesContext
注释。TransactionalTestExecutionListener
:使用默认回滚语义提供 transactional 测试执行。SqlScriptsTestExecutionListener
:运行使用@Sql
annotation 配置的 SQL 脚本。
注册TestExecutionListener实现
您可以使用@TestExecutionListeners
annotation 为 test class 及其子类注册自定义TestExecutionListener
__mplement。有关详细信息和示例,请参阅annotation 支持和@TestExecutionListeners的 javadoc。
自动发现默认的TestExecutionListener实现
使用@TestExecutionListeners
注册自定义TestExecutionListener
实现适用于在有限测试场景中使用的自定义 listener。但是,如果需要在测试套件中使用自定义 listener,则会变得很麻烦。自 Spring Framework 4.1 以来,通过支持通过SpringFactoriesLoader
机制自动发现默认TestExecutionListener
实现来解决此问题。
具体来说,spring-test
模块在org.springframework.test.context下声明所有核心的默认TestExecutionListener
实现。TestExecutionListener键的META-INF/spring.factories
属性文件。第三方框架和开发人员可以通过他们自己的META-INF/spring.factories
以同样的方式将他们自己的TestExecutionListener实现贡献给默认侦听器列表。工厂的属性文件。
OrderingTestExecutionListener实现
当的 TestContext framework 通过前述 SpringFactoriesLoader
机制发现默认TestExecutionListener
实现,实例化 listeners 通过使用 Spring 的AnnotationAwareOrderComparator
,里面供奉 Spring 的Ordered
接口和@Order
annotation 为 ordering 排序。 AbstractTestExecutionListener
和 Spring 提供的所有默认TestExecutionListener
实现将使用适当的值实现Ordered
。因此,Third-party 框架和开发人员应确保通过实现Ordered
或声明@Order
来确保其默认的TestExecutionListener
实现在正确的 order 中注册。有关为每个核心 listener 分配的值的详细信息,请参阅 javadoc 以获取核心缺省TestExecutionListener
实现的getOrder()
方法。
合并TestExecutionListener实现
如果通过@TestExecutionListeners
注册了自定义TestExecutionListener
,则不会注册默认的 listener。在大多数 common 测试场景中,这有效地迫使开发人员除了任何自定义 listener 之外还手动声明所有默认 listener。以下清单演示了这种 configuration 方式:
1 |
|
这种方法的挑战在于它要求开发人员确切地知道默认情况下注册了哪些 listeners。此外,默认 listeners 的集合可以在发行版之间更改 - 例如,在 Spring Framework 4.1 中引入,而DirtiesContextBeforeModesTestExecutionListener
在 Spring Framework 4.2 中引入。此外,像 Spring Security 这样的 third-party 框架通过使用前面提到的自动发现机制来注册它们自己的默认TestExecutionListener
实现。
为避免必须知道和 re-declare 所有默认 listeners,您可以将@TestExecutionListeners
的mergeMode
属性设置为MergeMode.MERGE_WITH_DEFAULTS
。 MERGE_WITH_DEFAULTS
表示本地声明的 listeners 应该与默认的 listeners 合并。合并算法确保从列表中删除重复项,并根据AnnotationAwareOrderComparator
的语义对生成的合并 listeners 组进行排序,如Ordering TestExecutionListener Implementations中所述。如果 listener 实现Ordered
或使用@Order
注解,它可以影响它与默认值合并的位置。否则,在合并时,本地声明的 listeners 将附加到默认 listeners 列表中。
对于 example,如果MyCustomTestExecutionListener
class 在先前 example 配置其order
value(对于 example,500
)至小于ServletTestExecutionListener
的 order(这恰好是1000
),则MyCustomTestExecutionListener
然后可以自动地与默认列表合并在ServletTestExecutionListener
前面,前一个 example 可以替换为以下内容:
1 |
|
Context管理
每个TestContext
为其负责的测试实例提供 context management 和缓存支持。测试实例不会自动接收对已配置的ApplicationContext
的访问权限。但是,如果测试 class 实现了ApplicationContextAware
接口,则会向测试实例提供ApplicationContext
的 reference。请注意AbstractJUnit4SpringContextTests
和AbstractTestNGSpringContextTests
实现了ApplicationContextAware
,因此可以自动提供对ApplicationContext
的访问。
@Autowired ApplicationContext
作为实现ApplicationContextAware
接口的替代方法,您可以通过字段或 setter 方法上的@Autowired
annotation 为您的 test class 注入 application context,如下面的 example 所示:
1 |
|
1 | 注入ApplicationContext 。 |
同样,如果您的测试配置为加载WebApplicationContext
,您可以 inject web application context 进入测试,如下所示:
1 |
|
1 | 配置WebApplicationContext 。 |
2 | 注入WebApplicationContext 。 |
使用@Autowired
的依赖注入由DependencyInjectionTestExecutionListener
提供,默认配置为DependencyInjectionTestExecutionListener
(参见测试夹具的依赖注入)。
使用 TestContext framework 的 Test classes 不需要扩展任何特定的 class 或实现特定的接口来配置它们的 application context。相反,configuration 是通过在 class level 处声明@ContextConfiguration
annotation 来实现的。如果 test class 没有显式声明 application context 资源位置或带注释的 classes,则配置的ContextLoader
确定如何从默认位置加载 context 或默认 configuration classes。除了 context 资源位置和带注释的 classes 之外,还可以通过 application context 初始化程序配置 application context。
以下部分说明如何使用 Spring 的@ContextConfiguration
annotation 通过使用 XML configuration files,Groovy 脚本,带注释的 classes(通常为@Configuration
classes)或 context 初始化程序来配置测试ApplicationContext
。或者,您可以为高级用例实现和配置自己的自定义SmartContextLoader
。
- Context Configuration with XML resources
- Context Configuration with Groovy Scripts
- Context Configuration with Annotated Classes
- 混合 XML,Groovy 脚本和带注释的 Classes
- Context Configuration with Context Initializers
- Context Configuration 继承
- Context Configuration with Environment Profiles
- Context Configuration with Test Property Sources
- Loading WebApplicationContext
- Context 缓存
- Context Hierarchies
使用XML资源配置Context
要使用 XML configuration files 为测试加载ApplicationContext
,请使用@ContextConfiguration
注释 test class,并使用包含 XML configuration 元数据的资源位置的 array 配置locations
属性。普通路径或相对路径(对于 example,context.xml
)被视为 classpath 资源,该资源相对于定义 test class 的包。以斜杠开头的路径被视为绝对 classpath 位置(对于 example,/org/example/config.xml
)。表示资源 URL 的路径(i.e.,前缀为classpath:
,file:
,http:
,etc.)的路径按原样使用。
1 |
|
1 | 将 locations 属性设置为 XML files 列表。 |
@ContextConfiguration
通过标准 Java value
属性支持locations
属性的别名。因此,如果您不需要在@ContextConfiguration
中声明其他属性,则可以省略locations
属性 name 的声明,并使用以下 example 中演示的缩写格式声明资源位置:
1 |
|
1 | 在不使用location 属性的情况下指定 XML files。 |
如果省略@ContextConfiguration
annotation 中的locations
和value
属性,则 TestContext framework 会尝试检测默认的 XML 资源位置。具体来说,GenericXmlContextLoader
和GenericXmlWebContextLoader
根据 test class 的 name 检测默认位置。如果 class 被命名为com.example.MyTest
,则GenericXmlContextLoader
从"classpath:com/example/MyTest-context.xml"
加载 application context。以下 example 显示了如何执行此操作:
1 | package com.example; |
1 | 从默认位置加载 configuration。 |
使用Groovy脚本配置Context
要使用使用Groovy Bean 定义 DSL的 Groovy 脚本为测试加载ApplicationContext
,可以使用@ContextConfiguration
注释 test class,并使用包含 Groovy 脚本资源位置的 array 配置locations
或value
属性。 Groovy 脚本的资源查找语义与XML configuration files中描述的相同。
启用 Groovy 脚本支持 如果 Groovy 位于 classpath 上,则支持使用 Groovy 脚本加载 Spring TestContext Framework 中的
ApplicationContext
。
以下 example 显示了如何指定 Groovy configuration files:
1 |
|
1 | 指定 Groovy configuration files 的位置。 |
如果省略@ContextConfiguration
annotation 中的locations
和value
属性,则 TestContext framework 会尝试检测默认的 Groovy 脚本。具体来说,GenericGroovyXmlContextLoader
和GenericGroovyXmlWebContextLoader
根据 test class 的 name 检测默认位置。如果您的 class 名为com.example.MyTest
,则 Groovy context 加载程序会从"classpath:com/example/MyTestContext.groovy"
加载 application context。以下 example 显示了如何使用默认值:
1 | package com.example; |
1 | 从默认位置加载 configuration。 |
同时声明 XML configuration 和 Groovy 脚本
您可以使用@ContextConfiguration
的locations
或value
属性同时声明 XML configuration files 和 Groovy 脚本。如果配置的资源位置的路径_end 为.xml
,则使用XmlBeanDefinitionReader
加载。否则,使用GroovyBeanDefinitionReader
加载它。
以下清单显示了如何在 integration 测试中组合两者:
1 |
|
使用Annotated类配置Context
要使用带注释的 classes(请参阅Java-based container configuration)为测试加载ApplicationContext
,可以使用@ContextConfiguration
注释 test class,并使用 array 配置包含引用 classes 的 references 的_ar属性。以下 example 显示了如何执行此操作:
1 |
|
1 | 指定带注释的 classes。 |
带注释的 Classes
术语“带注释的 class”可以指以下任何一种:
- 用
@Configuration
注释的 class。 - 一个 component(即 class 用
@Component
,@Service
,@Repository
或其他构造型 annotations 注释)。 - 一个符合 JSR-330 的 class,用
javax.inject
annotations 注释。 - 包含
@Bean
方法的任何其他 class。
有关注释 classes 的 configuration 和语义的更多信息,请参阅@Configuration和@Bean的 javadoc,特别注意@Bean
Lite 模式的讨论。
如果省略@ContextConfiguration
annotation 中的classes
属性,则 TestContext framework 会尝试检测是否存在 default configuration classes。具体来说,AnnotationConfigContextLoader
和AnnotationConfigWebContextLoader
检测符合@Configuration javadoc 中指定的 configuration class implementations 要求的 test class 的所有static
嵌套 classes。请注意 configuration class 的 name 是任意的。此外,如果需要,test class 可以包含多个static
嵌套 configuration class。在下面的示例中,OrderServiceTest
class 声明一个名为Config
的static
嵌套 configuration class,它自动用于加载测试 class 的ApplicationContext
:
1 |
|
1 | _Lading 来自嵌套 class 的 configuration 信息。 |
混合XML,Groovy脚本和带注解的Classes
有时可能需要混合使用 XML configuration files,Groovy 脚本和带注释的 classes(通常为@Configuration
classes)来为测试配置ApplicationContext
。例如,如果在 production 中使用 XML configuration,则可能决定要使用@Configuration
classes 为测试配置特定的 Spring-managed 组件,反之亦然。
此外,一些 third-party 框架(例如 Spring Boot)提供 first-class 支持同时从不同类型的资源中加载ApplicationContext
(例如,XML configuration files,Groovy 脚本和@Configuration
classes)。历史上,Spring Framework 对标准部署不支持此功能。因此,Spring Framework 在spring-test
模块中提供的大多数SmartContextLoader
实现只支持每个测试 context 的一种资源类型。但是,这并不意味着您不能同时使用两者。一般规则的一个例外是GenericGroovyXmlContextLoader
和GenericGroovyXmlWebContextLoader
同时支持 XML configuration files 和 Groovy 脚本。此外,third-party 框架可以选择支持locations
和classes
到@ContextConfiguration
的声明,并且,通过 TestContext framework 中的标准测试支持,您有以下选项。
如果要使用资源位置(对于 example,XML 或 Groovy)和@Configuration
classes 来配置测试,则必须选择一个作为入口点,并且必须包含或 import 另一个。例如,在 XML 或 Groovy 脚本中,您可以通过使用 component 扫描包含@Configuration
classes 或将它们定义为普通的 Spring beans,而在@Configuration
class 中,您可以使用@ImportResource
来 import XML configuration files 或 Groovy 脚本。请注意,此行为在语义上等同于在 production 中配置 application 的方式:在 production configuration 中,您可以定义一组 XML 或 Groovy 资源位置,或者从中加载 production ApplicationContext
的一组@Configuration
classes,但是您仍然拥有自由包含或 import 其他类型的 configuration。
使用Context初始化器配置Context
要使用 context 初始化程序为测试配置ApplicationContext
,请使用@ContextConfiguration
注释 test class,并使用包含 references 的 array 配置initializers
属性以实现ApplicationContextInitializer
。然后使用声明的 context 初始值设定项初始化为测试加载的ConfigurableApplicationContext
。请注意,每个声明的初始化程序支持的具体ConfigurableApplicationContext
类型必须与正在使用的SmartContextLoader
(通常为GenericApplicationContext
)创建的ApplicationContext
类型兼容。此外,调用初始值设定项的 order 取决于它们是实现 Spring 的Ordered
接口还是使用 Spring 的@Order
annotation 或标准@Priority
annotation 注释。以下 example 显示了如何使用初始值设定项:
1 |
|
1 | 使用 configuration class 和初始化程序指定 configuration。 |
你也可以在@ContextConfiguration
中完全省略 XML configuration files,Groovy 脚本或带注释的 classes 的声明,而只是声明ApplicationContextInitializer
classes,它们负责在 context 中注册 beans - for example,通过从 XML files 编程 loading bean 定义或 configuration classes。以下 example 显示了如何执行此操作:
1 |
|
1 | 仅使用初始化程序指定 configuration。 |
Context配置继承
@ContextConfiguration
支持 boolean inheritLocations
和inheritInitializers
属性,这些属性表示是否应继承资源位置或由超类声明的带注释的 classes 和 context 初始值设定项。两个标志的默认 value 是true
。这意味着 test class 继承资源位置或带注释的 classes 以及任何超类声明的 context 初始值设定项。具体而言,test class 的资源位置或带注释的 classes 将附加到资源位置列表或超类声明的带注释的 classes。类似地,给定测试 class 的初始化器被添加到由测试超类定义的初始化器集合中。因此,子类可以选择扩展资源位置,带注释的 classes 或 context 初始值设定项。
如果@ContextConfiguration
中的inheritLocations
或inheritInitializers
属性设置为false
,则资源位置或带注释的 classes 和 context 初始值分别为 test class 阴影,并有效地替换超类定义的 configuration。
在下一个使用 XML 资源位置的 example 中,ApplicationContext
for ExtendedTest
是从base-config.xml
和extended-config.xml
加载到该 order 中的。因此,在extended-config.xml
中定义的 Beans 可以覆盖(即替换)base-config.xml
中定义的那些。以下 example 显示了一个 class 如何扩展另一个 class 并使用它自己的 configuration 文件和超类的 configuration 文件:
1 |
|
1 | 超类中定义的配置文件。 |
2 | 子类中定义的配置文件。 |
类似地,在使用带注释的 classes 的下一个 example 中,ApplicationContext
for ExtendedTest
是从BaseConfig
和ExtendedConfig
classes 中加载的,在 order 中。因此,在ExtendedConfig
中定义的 Beans 可以覆盖(即替换)BaseConfig
中定义的那些。以下 example 显示了一个 class 如何扩展另一个 class 并使用它自己的 configuration class 和超类的 configuration class:
1 |
|
1 | Configuration class 在超类中定义。 |
2 | Configuration class 在子类中定义。 |
在使用 context 初始值设定项的下一个 example 中,使用BaseInitializer
和ExtendedInitializer
初始化ApplicationContext
for ExtendedTest
。但请注意,调用初始值设定项的 order 取决于它们是实现 Spring 的Ordered
接口还是使用 Spring 的@Order
annotation 或标准@Priority
annotation 注释。下面的 example 显示了一个 class 如何扩展另一个 class 并使用它自己的初始化器和超类的初始化器:
1 |
|
1 | 初始化程序在超类中定义。 |
2 | 初始化器在子类中定义。 |
使用环境配置文件进行上下文配置
Spring 3.1 在 framework 中引入了 first-class 支持环境概念和 profiles(AKA“ bean definition profiles”),并且 integration 测试可以配置为激活特定的 bean 定义 profiles 用于各种测试场景。这是通过使用@ActiveProfiles
annotation 注释 test class 并提供_ _ofofiles 列表来实现的,这些 profiles 列表应该在为测试加载时激活。
您可以将
@ActiveProfiles
用于新SmartContextLoader
SPI 的任何 implementation,但ContextLoader
SPI 的 implementations 不支持@ActiveProfiles
。
考虑使用 XML configuration 和@Configuration
classes 的两个示例:
1 | <!-- app-config.xml --> |
1 | package com.bank.service; |
当TransferServiceTest
是 run 时,它的ApplicationContext
从 classpath 的根目录中的app-config.xml
configuration 文件加载。如果检查app-config.xml
,则可以看到accountRepository
bean 依赖于dataSource
bean。但是,dataSource
未定义为 top-level bean。相反,dataSource
被定义三次:在production
profile 中,在dev
profile 中,以及default
profile 中。
通过使用@ActiveProfiles("dev")
注释TransferServiceTest
,我们指示 Spring TestContext Framework 加载ApplicationContext
并将 active profiles 设置为{"dev"}
。因此,创建嵌入式数据库并使用测试数据填充,accountRepository
bean 与_re开发DataSource
连接。这可能是我们在 integration 测试中想要的。
将 beans 分配给default
profile 有时很有用。仅当没有专门激活其他 profile 时,才会包含默认 profile 中的 Beans。您可以使用它来定义要在 application 的默认 state 中使用的“fallback”beans。对于 example,您可以显式提供dev
和production
profiles 的数据源,但如果这两个数据源都不是 active,则将 in-memory 数据源定义为默认值。
以下 code 列表演示了如何使用@Configuration
classes 而不是 XML 实现相同的 configuration 和 integration 测试:
1 |
|
在此变体中,我们将 XML configuration 拆分为四个独立的@Configuration
classes:
TransferServiceConfig
:使用@Autowired
通过依赖注入获取dataSource
。StandaloneDataConfig
:为适合开发人员测试的嵌入式数据库定义dataSource
。JndiDataConfig
:定义在 production 环境中从 JNDI 检索的dataSource
。DefaultDataConfig
:如果没有 profile 是 active,则为默认的嵌入式数据库定义dataSource
。
与 XML-based configuration example 一样,我们仍然使用@ActiveProfiles("dev")
注释TransferServiceTest
,但是这个 time 我们使用@ContextConfiguration
annotation 指定所有四个 configuration classes。测试 class 本身保持完全不变。
通常情况下,在给定项目中的多个 test classes 中使用一组 profiles。因此,为了避免@ActiveProfiles
annotation 的重复声明,您可以在 base class 上声明@ActiveProfiles
一次,并且子类自动从 base class 继承@ActiveProfiles
configuration。在下面的示例中,@ActiveProfiles
(以及其他 annotations)的声明已被移动到抽象超类AbstractIntegrationTest
:
1 | package com.bank.service; |
@ActiveProfiles
还支持可用于禁用 active profiles 继承的inheritProfiles
属性,如下面的 example 所示:
1 | package com.bank.service; |
此外,有时需要以编程方式而不是声明性地为测试解析 active profiles - 例如,基于:
- 目前的操作系统。
- 是否在连续的 integration build 服务器上执行测试。
- 存在某些环境变量。
- 自定义 class-level 注释的存在。
- 其他问题。
要以编程方式解析 active bean definition profiles,您可以实现自定义ActiveProfilesResolver
并使用@ActiveProfiles
的resolver
属性进行注册。有关详细信息,请参阅相应的javadoc。以下 example 演示了如何实现和注册自定义OperatingSystemActiveProfilesResolver
:
1 | package com.bank.service; |
使用测试Property资源配置Context
Spring 3.1 在 framework 中引入了 first-class 支持,用于具有 property 源层次结构的环境的概念。从 Spring 4.1 开始,您可以使用 test-specific property 源配置 integration 测试。与@Configuration
classes 上使用的@PropertySource
annotation 相比,您可以在 test class 上声明@TestPropertySource
annotation,以声明 test properties files 或 inlined properties 的资源位置。这些测试 property 源被添加到Environment
中PropertySources
的集合中,用于为注释的 integration 测试加载的ApplicationContext
。
您可以将
@TestPropertySource
与SmartContextLoader
SPI 的任何 implementation 一起使用,但ContextLoader
SPI 的 implementations 不支持@TestPropertySource
。
的实现通过MergedContextConfiguration
中的getPropertySourceLocations()
和getPropertySourceProperties()
方法获得对合并的测试属性源值的访问。
声明测试属性来源
您可以使用@TestPropertySource
的locations
或value
属性配置 test properties files。
支持传统和 XML-based properties 文件格式 - 例如,"classpath:/com/example/test.properties"
或"file:///path/to/file.xml"
。
每个路径都被解释为 Spring Resource
。普通路径(对于 example,"test.properties"
)被视为 classpath 资源,该资源相对于定义 test class 的包。以斜杠开头的路径被视为绝对 classpath 资源(对于 example:"/org/example/test.xml"
)。使用指定的资源协议加载 references URL(对于 example,前缀为classpath:
,file:
或http:
的路径)的路径。不允许使用资源位置通配符(例如*/.properties
):每个位置必须仅评估一个.properties
或.xml
资源。
以下 example 使用 test properties 文件:
1 |
|
1 | 使用绝对路径指定 properties 文件。 |
您可以使用@TestPropertySource
的properties
属性以 key-value 对的形式配置内联 properties,如下一个 example 所示。所有 key-value 对都作为具有最高优先级的单个测试PropertySource
添加到封闭的Environment
中。
key-value 对支持的语法与为 Java properties 文件中的条目定义的语法相同:
key=value
key:value
key value
以下 example sets 两个内联 properties:
1 |
|
1 | 使用 key-value 语法的两个变体设置两个 properties。 |
默认Properties文件检测
如果将@TestPropertySource
声明为空 annotation(即,没有locations
或properties
属性的显式值),则会尝试检测相对于声明 annotation 的 class 的默认 properties 文件。例如,如果带注释的测试 class 为com.example.MyTest
,则相应的默认 properties 文件为classpath:com/example/MyTest.properties
。如果无法检测到默认值,则抛出IllegalStateException
。
优先
测试 property 源的优先级高于从操作系统环境加载的源,Java 系统 properties 或_appperty 源由 application 以声明方式使用@PropertySource
或以编程方式添加。因此,test property 源可用于有选择地覆盖 system 和 application property 源中定义的 properties。此外,内联 properties 的优先级高于从资源位置加载的 properties。
在下一个示例中,"/test.properties"
和port
properties 以及"/test.properties"
中定义的任何 properties 都会覆盖在 system 和 application property 源中定义的同一 name 的任何 properties。此外,如果"/test.properties"
文件定义了timezone
和port
properties 的条目,那么这些条目将被使用properties
属性声明的内联 properties 覆盖。以下 example 显示了如何在文件和内联中指定 properties:
1 |
|
继承和覆盖测试PropertySources
@TestPropertySource
支持 boolean inheritLocations
和inheritProperties
属性,表示是否应继承 properties files 和超类声明的内联 properties 的资源位置。两个标志的默认 value 是true
。这意味着 test class 继承了任何超类声明的位置和内联 properties。具体来说,test class 的位置和内联 properties 将附加到超类声明的位置和内联 properties。因此,子类可以选择扩展位置和内联 properties。请注意,后面出现的 properties 会影响(即覆盖)之前出现的同一 name 的 properties。此外,上述优先规则也适用于继承的 test property 源。
如果@TestPropertySource
中的inheritLocations
或inheritProperties
属性设置为false
,则分别为 test class 阴影的位置或内联 properties,并有效地替换超类定义的 configuration。
在下一个 example 中,BaseTest
for BaseTest
是仅使用base.properties
文件作为 test property 源加载的。相反,使用base.properties
和extended.properties
files 作为 test property 源位置来加载ApplicationContext
for ExtendedTest
。以下 example 显示了如何使用properties
files 在子类及其超类中定义 properties:
1 |
|
在下一个 example 中,BaseTest
for BaseTest
仅使用内联的key1
property 加载。相反,ApplicationContext
for ExtendedTest
是使用内联key1
和key2
properties 加载的。以下 example 显示了如何使用内联 properties 在子类及其超类中定义 properties:
1 |
|
加载WebApplicationContext
Spring 3.2 在 integration 测试中引入了的支持。要指示 TestContext framework 加载WebApplicationContext
而不是标准ApplicationContext
,您可以使用@WebAppConfiguration
注释相应的 test class。
测试 class 上存在@WebAppConfiguration
指示 TestContext framework(TCF)应该为 integration 测试加载WebApplicationContext
(WAC)。在后台,TCF 确保创建MockServletContext
并将其提供给测试的 WAC。默认情况下,MockServletContext
的基本资源路径设置为src/main/webapp
。这被解释为相对于 JVM 根目录的路径(通常是项目的路径)。如果您熟悉 Maven 项目中 web application 的目录结构,则您知道src/main/webapp
是 WAR 根目录的默认位置。如果需要覆盖此默认值,则可以提供@WebAppConfiguration
annotation 的备用路径(对于 example,@WebAppConfiguration("src/test/webapp")
)。如果您希望从 classpath 而不是文件系统引用基本资源路径,则可以使用 Spring 的classpath:
前缀。
请注意,Spring 对WebApplicationContext
implementations 的测试支持与其对标准ApplicationContext
implementations 的支持相同。使用WebApplicationContext
进行测试时,可以使用@ContextConfiguration
自由声明 XML configuration files,Groovy 脚本或@Configuration
classes。您也可以自由使用任何其他测试注释,例如@ActiveProfiles
,@TestExecutionListeners
,@Sql
,@Rollback
等。
本节中的其余示例显示了的各种 configuration 选项。以下 example 显示了 TestContext framework 对_coniguration 约定的支持:
例 1.公约
1 |
|
如果使用@WebAppConfiguration
注释测试 class 而未指定资源基路径,则资源路径有效地默认为file:src/main/webapp
。同样,如果在未指定资源locations
,带注释的classes
或 context initializers
的情况下声明@ContextConfiguration
,则 Spring 会尝试使用约定(即与WacTests
class 或静态嵌套@Configuration
classes 相同的包中的WacTests-context.xml
)检测 configuration 的存在。
以下 example 显示了如何使用@WebAppConfiguration
显式声明资源基本路径以及使用@ContextConfiguration
显示 XML 资源位置:
Example 2.默认资源语义
1 |
|
这里要注意的重要一点是_path 与这两个注释的不同语义。默认情况下,@WebAppConfiguration
resource paths 是基于文件系统的,而@ContextConfiguration
资源位置是基于 classpath 的。
以下 example 显示我们可以通过指定 Spring 资源前缀来覆盖两个 annotations 的默认资源语义:
Example 3.显式资源语义
1 |
|
将此 example 中的 comments 与前一个 example 对比。
使用 Web Mocks
为了提供全面的 web 测试支持,Spring 3.2 引入了默认启用的ServletTestExecutionListener
。在针对WebApplicationContext
进行测试时,在每个测试方法之前使用 Spring Web 的RequestContextHolder
来设置TestExecutionListener state 默认 thread-local state,并根据使用@WebAppConfiguration
配置的基本资源路径创建MockHttpServletRequest
,MockHttpServletResponse
和ServletWebRequest
。 ServletTestExecutionListener
还确保可以将MockHttpServletResponse
和ServletWebRequest
注入到测试实例中,并且一旦测试完成,它就会清除 thread-local state。
一旦为测试加载WebApplicationContext
,您可能会发现需要与 web 模拟进行交互 - 例如,设置测试夹具或在调用 web component 后执行断言。以下 example 显示哪些模拟可以自动连接到您的测试实例中。请注意,WebApplicationContext
和MockServletContext
都在测试套件中缓存,而其他模拟由ServletTestExecutionListener
按照测试方法进行管理。
1 |
|
Context缓存
一旦 TestContext framework 为测试加载ApplicationContext
(或WebApplicationContext
),该 context 就会被缓存并重用于在同一测试套件中声明相同唯一 context configuration 的所有后续测试。要了解缓存的工作原理,了解“独特”和“测试套件”的含义非常重要。
可以通过用于加载它的 configuration 参数的组合来唯一标识。因此,configuration 参数的唯一组合用于生成 key,高速缓存 context。 TestContext framework 使用以下 configuration 参数来 build context cache key:
locations
(来自@ContextConfiguration
)classes
(来自@ContextConfiguration
)contextInitializerClasses
(来自@ContextConfiguration
)contextCustomizers
(来自ContextCustomizerFactory
)contextLoader
(来自@ContextConfiguration
)parent
(来自@ContextHierarchy
)activeProfiles
(来自@ActiveProfiles
)propertySourceLocations
(来自@TestPropertySource
)propertySourceProperties
(来自@TestPropertySource
)resourceBasePath
(来自@WebAppConfiguration
)
例如,如果TestClassA
为@ContextConfiguration
的locations
(或value
)属性指定{"app-config.xml", "test-config.xml"}
,则 TestContext framework 将相应的ApplicationContext
和 store 加载到仅基于这些位置的 key 下的static
context 缓存中。因此,如果TestClassB
也为其位置定义了{"app-config.xml", "test-config.xml"}
(通过继承显式或隐式)但没有定义@WebAppConfiguration
,不同的ContextLoader
,不同的 active profiles,不同的 context 初始化器,不同的 test property 源或不同的 parent context,那么相同ApplicationContext
由两个 test classes 共享。这意味着 loading application context 的设置成本仅产生一次(每个测试套件),后续测试执行速度要快得多。
测试套件和分叉流程
Spring TestContext framework stores application 上下文在静态缓存中。这意味着 context 实际上存储在static
变量中。换句话说,如果测试在单独的进程中执行,则在每次测试执行之间清除静态高速缓存,这有效地禁用了高速缓存机制。
要从缓存机制中受益,所有测试必须在同一个 process 或测试套件中运行。这可以通过在 IDE 中作为 group 执行所有测试来实现。类似地,当使用_bum framework(例如 Ant,Maven 或 Gradle)执行测试时,确保 build framework 不会在测试之间进行分配是很重要的。例如,如果 Maven Surefire plug-in 的forkMode设置为always
或pertest
,则 TestContext framework 无法在 test classes 之间缓存 application 上下文,因此 build process 的运行速度明显更慢。
从 Spring Framework 4.3 开始,context 缓存的大小受限于默认的最大大小 32.每当达到最大大小时,最近最少使用(LRU)驱逐 policy 用于驱逐和关闭陈旧的上下文。您可以通过设置名为spring.test.context.cache.maxSize
的 JVM 系统 property,从命令 line 或 build 脚本配置最大大小。或者,您可以使用SpringProperties
API 以编程方式设置相同的 property。
由于在给定的测试套件中加载了大量的 application 上下文会导致套件执行不必要的 long time 执行,因此确切地知道已经加载和缓存了多少个上下文通常是有益的。要查看基础 context 缓存的统计信息,可以将org.springframework.test.context.cache
logging 类别的 log level 设置为DEBUG
。
在不太可能的情况下,测试会破坏 application context 并需要重新加载(例如,通过修改 bean 定义或 application object 的 state),您可以使用@DirtiesContext
注解 test class 或 test 方法(请参阅@DirtiesContext
的讨论) Spring Testing Annotations)。这指示 Spring 从缓存中删除 context 并在 running 下一个测试之前重建 application context。请注意,DirtiesContextBeforeModesTestExecutionListener
和DirtiesContextTestExecutionListener
提供了对@DirtiesContext
annotation 的支持,这些默认情况下已启用。
Context层次结构
在编写依赖于加载的 Spring ApplicationContext
的 integration 测试时,通常可以对单个 context 进行测试。但是,有时候对ApplicationContext
实例的层次结构进行测试是有益的,甚至是必要的。例如,如果你正在开发一个 Spring MVC web application,你通常会有一个由 Spring 的ContextLoaderListener
加载的WebApplicationContext
和一个由 Spring 的DispatcherServlet
加载的 child WebApplicationContext
。这会产生 parent-child context 层次结构,其中共享组件和基础结构 configuration 在根 context 中声明,并在 child context 中由 web-specific 组件使用。另一个用例可以在 Spring Batch applications 中找到,其中您经常有一个 parent context 为共享批处理基础结构提供 configuration,而 child context 用于 configuration 特定批处理 job。
从 Spring Framework 3.2.2 开始,您可以通过在单个 test class 或 test class 层次结构中使用@ContextHierarchy
annotation 声明 context configuration 来编写使用 context 层次结构的 integration 测试。如果在 test class 层次结构中的多个 classes 上声明 context 层次结构,则还可以合并或覆盖 context 层次结构中特定的名为 level 的 context configuration。合并层次结构中给定 level 的 configuration 时,configuration 资源类型(即 XML configuration files 或 annotated classes)必须一致。否则,在使用不同资源类型配置的 context 层次结构中具有不同的级别是完全可以接受的。
本节中剩余的 JUnit 4-based 示例显示了需要使用 context 层次结构的 integration 测试的 common configuration 方案。
使用 context 层次结构单个测试 class
ControllerIntegrationTests
表示 Spring MVC web application 的典型 integration 测试场景,声明 context 层次结构包含两个级别,一个用于根WebApplicationContext
(使用TestAppConfig
@Configuration
class 加载),另一个用于调度程序 servlet WebApplicationContext
(通过使用WebConfig
@Configuration
class)。自动装入测试实例的WebApplicationContext
是 child context(即层次结构中最低的 context)的WebApplicationContext
。以下清单显示了此 configuration 方案:
1 |
|
具有隐式 parent context 的 Class 层次结构
此 example 中的测试 classes 在 test class 层次结构中定义 context 层次结构。 AbstractWebTests
在 Spring-powered web application 中声明了根WebApplicationContext
的 configuration。但请注意,AbstractWebTests
未声明@ContextHierarchy
。因此,AbstractWebTests
的子类可以选择参与 context 层次结构或遵循@ContextConfiguration
的标准语义。 SoapWebServiceTests
和RestWebServiceTests
都扩展AbstractWebTests
并使用@ContextHierarchy
定义 context 层次结构。结果是加载了三个 application 上下文(每个@ContextConfiguration
声明一个),并且基于AbstractWebTests
中的 configuration 加载的 application context 被设置为为具体子类加载的每个上下文的 parent context。以下清单显示了此 configuration 方案:
1 |
|
具有合并 context 层次结构 configuration 的 Class 层次结构
此 example 中的 classes 显示在 order 中使用命名层次结构级别来合并 context 层次结构中特定级别的 configuration。 BaseTests
在层次结构中定义了两个级别parent
和child
。 ExtendedTests
扩展BaseTests
并指示 Spring TestContext Framework 合并child
层次 level 的 context configuration,方法是确保@ContextConfiguration
中name
属性中声明的名称都是child
。结果是加载了三个 application 上下文:一个用于/app-config.xml
,一个用于/user-config.xml
,一个用于{"/user-config.xml", "/order-config.xml"}
。与前面的 example 一样,从/app-config.xml
加载的 application context 被设置为从/user-config.xml
和{"/user-config.xml", "/order-config.xml"}
加载的上下文的 parent context。以下清单显示了此 configuration 方案:
1 |
|
具有重写 context 层次结构 configuration 的 Class 层次结构
与前面的示例相比,此 example 演示了如何通过将@ContextConfiguration
中的inheritLocations
flag 设置为false
来覆盖 context 层次结构中给定命名 level 的 configuration。因此,ExtendedTests
的 application context 仅从/test-user-config.xml
加载,并且 parent 设置为从/app-config.xml
加载的 context。以下清单显示了此 configuration 方案:
1 |
|
在 context 层次结构中消除 context 如果在 context 配置为 context 层次结构的一部分的测试中使用
@DirtiesContext
,则可以使用hierarchyMode
flag 来控制 context 缓存的清除方式。有关更多详细信息,请参阅Spring Testing Annotations中的@DirtiesContext
和@DirtiesContext javadoc 的讨论。
测试fixture的依赖注入
当您使用DependencyInjectionTestExecutionListener
(默认配置)时,测试实例的依赖项将从您使用@ContextConfiguration
配置的 application context 中的 beans 注入。您可以使用 setter injection,field injection 或两者,具体取决于您选择的注释以及是否将它们放在 setter 方法或字段上。为了与 Spring 2.5 和 3.0 中引入的 annotation 支持保持一致,您可以使用 Spring 的@Autowired
annotation 或 JSR 330 中的@Inject
annotation。
TestContext framework 没有检测实例化测试实例的方式。因此,对构造函数使用
@Autowired
或@Inject
对 test classes 没有影响。
因为@Autowired
用于执行按类型自动装配,如果您有多个相同类型的 bean 定义,则不能依赖此方法来处理这些特定的 beans。在这种情况下,您可以将@Autowired
与@Qualifier
结合使用。从 Spring 3.0 开始,您也可以选择将@Inject
与@Named
结合使用。或者,如果您的 test class 可以访问其ApplicationContext
,则可以使用(for example)调用applicationContext.getBean("titleRepository")
来执行显式查找。
如果您不希望将依赖项注入应用于测试实例,请不要使用@Autowired
或@Inject
注释字段或 setter 方法。或者,您可以通过使用@TestExecutionListeners
显式配置 class 并从 listeners 列表中省略DependencyInjectionTestExecutionListener.class
来完全禁用依赖项注入。
考虑测试HibernateTitleRepository
class 的场景,如目标部分所述。接下来的两个 code 列表演示了在字段和 setter 方法上使用@Autowired
。 application context configuration 在所有 sample code 列表之后显示。
以下 code 列表中的依赖项注入行为并非特定于 JUnit 4.相同的 DI 技术可以与任何测试 framework 一起使用。
以下示例将 calls 设置为静态断言方法,例如assertNotNull()
,但不使用Assert
预先调用。在这种情况下,假设该方法是通过_示例中未显示的import static
声明正确导入的。
第一个 code 列表显示了一个基于 JUnit 4 的_Ilass 实现,它使用@Autowired
进行字段注入:
1 |
|
或者,您可以将 class 配置为使用@Autowired
进行 setter 注入,如下所示:
1 |
|
前面的 code 列表使用@ContextConfiguration
annotation 引用的相同 XML context 文件(即repository-config.xml
)。以下显示了此 configuration:
1 |
|
如果从正在其中一个 setter 方法上使用
@Autowired
的 Spring-provided test base class 进行扩展,则可能在 application context 中定义了多个受影响类型的 beans(对于 example,多个DataSource
beans)。在这种情况下,您可以覆盖 setter 方法并使用@Qualifier
annotation 来指示特定的目标 bean,如下所示(但请确保也委托给超类中的重写方法):
1 | // ... |
指定的限定符 value 表示特定的DataSource
bean 到 inject,将类型匹配集缩小到特定的 bean。它的 value 与相应<bean>
定义中的<qualifier>
声明匹配。 bean name 用作回退限定符 value,因此您还可以有效地指向 name by name(如前所示,假设myDataSource
是 bean id
)。
测试Request和Session-scopedBeans
Spring 从早年就开始支持请求 - 和 session-scoped beans。从 Spring 3.2 开始,您可以按照以下步骤测试 request-scoped 和 session-scoped beans:
- 通过使用
@WebAppConfiguration
注释 test class,确保为测试加载了WebApplicationContext
。 - 将 mock 请求或 session 注入测试实例并根据需要准备测试夹具。
- 调用从配置的
WebApplicationContext
中检索到的 web component(带依赖注入)。 - 对模拟执行断言。
下一个 code 代码段显示了登录用例的 XML configuration。请注意,userService
bean 依赖于 request-scoped loginAction
bean。此外,LoginAction
通过使用SpEL 表达式来实例化,该SpEL 表达式从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望通过 TestContext framework 管理的 mock 配置这些请求参数。以下清单显示了此用例的 configuration:
Example 5. Request-scoped bean configuration
1 | <beans> |
在RequestScopedBeanTests
中,我们将UserService
(即被测试对象)和MockHttpServletRequest
都注入到我们的测试实例中。在我们的requestScope()
测试方法中,我们通过在提供的MockHttpServletRequest
中设置请求参数来设置我们的测试夹具。当我们在userService
上调用loginUser()
方法时,我们可以确保用户服务可以访问当前MockHttpServletRequest
的 request-scoped loginAction
(也就是我们刚刚设置参数的那个)。然后,我们可以根据用户名和密码的已知输入对结果执行断言。以下清单显示了如何执行此操作:
Example 6. Request-scoped bean test
1 |
|
以下 code 代码段类似于我们之前为 request-scoped bean 看到的代码片段。但是,这个 time,userService
bean 依赖于 session-scoped userPreferences
bean。请注意,UserPreferences
bean 是使用 SpEL 表达式实例化的,该表达式从当前 HTTP session 中检索主题。在我们的测试中,我们需要在 TestContext framework 管理的 mock session 中配置主题。以下 example 显示了如何执行此操作:
例 7. Session-scoped bean configuration
1 | <beans> |
在SessionScopedBeanTests
中,我们将UserService
和MockHttpSession
注入到我们的测试实例中。在我们的sessionScope()
测试方法中,我们通过在提供的MockHttpSession
中设置预期的theme
属性来设置我们的测试夹具。当我们在userService
上调用processUserPreferences()
方法时,我们确信用户服务可以访问当前MockHttpSession
的 session-scoped userPreferences
,并且我们可以根据配置的主题对结果执行断言。以下 example 显示了如何执行此操作:
例 8. Session-scoped bean 测试
1 |
|
事务管理
在 TestContext framework 中,transactions 由TransactionalTestExecutionListener
管理,默认情况下配置TransactionalTestExecutionListener
,即使您没有在 test class 上显式声明@TestExecutionListeners
。但是,要启用对 transactions 的支持,必须在ApplicationContext
中配置PlatformTransactionManager
bean,并加载@ContextConfiguration
语义(稍后会提供更多详细信息)。此外,您必须在 class 或方法 level 中为测试声明 Spring 的@Transactional
annotation。
Test-managed事物
Test-managed transactions 是 transactions,通过使用TransactionalTestExecutionListener
以编程方式管理,或者使用TestTransaction
(稍后描述)以编程方式管理。你不应该将这样的 transactions 与 Spring-managed transactions(由ApplicationContext
加载的 Spring 直接管理的测试)或 application-managed transactions(那些由测试调用的 application code 中以编程方式管理的那些)混淆。 Spring-managed 和 application-managed transactions 通常参与 test-managed transactions。但是,如果 Spring-managed 或 application-managed transactions 配置了除REQUIRED
或SUPPORTS
之外的任何传播类型,则应谨慎使用(有关详细信息,请参阅有关transaction 传播的讨论)。
启用和禁用Transactions
使用@Transactional
注释测试方法会导致测试在 transaction 中运行,默认情况下,在完成测试后会自动回滚。如果使用@Transactional
注释了测试 class,则该 class 层次结构中的每个测试方法都在 transaction 中运行。未使用@Transactional
(在 class 或方法 level 上)注释的测试方法在 transaction 中不是 run。此外,使用@Transactional
注释但propagation
类型设置为NOT_SUPPORTED
的测试在 transaction 中不是 run。
请注意,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests已预先配置为 class level 的 transactional 支持。
以下 example 演示了为 Hibernate-based UserRepository
编写 integration 测试的 common 场景:
1 |
|
如Transaction 回滚和提交行为中所述,在运行createUser()
方法后无需清理数据库,因为对数据库所做的任何更改都会由TransactionalTestExecutionListener
自动回滚。
Transaction回滚和提交行为
默认情况下,test transactions 将在测试完成后自动回滚;但是,transactional commit 和 rollback behavior 可以通过@Commit
和@Rollback
注解以声明方式配置。有关详细信息,请参阅annotation 支持部分中的相应条目。
编程式Transaction管理
从 Spring Framework 4.1 开始,您可以使用TestTransaction
中的静态方法以编程方式与 test-managed transactions 进行交互。例如,您可以在测试方法中使用TestTransaction
,在方法之前,在方法之后使用TestTransaction
来启动或结束当前 test-managed transaction,或者为回滚或提交配置当前 test-managed transaction。只要启用TransactionalTestExecutionListener
,就会自动提供对TestTransaction
的支持。
以下 example 演示了TestTransaction
的一些 features。有关更多详细信息,请参阅 javadoc 以获取TestTransaction。
1 |
|
运行Code外的事物
有时,您可能需要在 transactional 测试方法之前或之后执行某些 code,但在 transactional context 之外 - 为 example,在运行测试之前验证初始数据库 state 或在测试运行后验证预期的 transactional 提交行为(如果 test 配置为提交 transaction)。对于这种情况,TransactionalTestExecutionListener
支持@BeforeTransaction
和@AfterTransaction
注释。您可以使用其中一个注释在 test class 或测试接口中的任何void
default 方法中注释任何void
方法,并且TransactionalTestExecutionListener
确保您的 before transaction 方法或 transaction 方法在适当的 time 运行。
之前的任何方法(例如使用 JUnit Jupiter 的
@BeforeEach
注释的方法)和任何后续方法(例如使用 JUnit Jupiter 的@AfterEach
注释的方法)都在 transaction 中运行。此外,对于未在 transaction 中配置为 run 的测试方法,@BeforeTransaction
或@AfterTransaction
注释的方法不是 run。
配置事务管理
TransactionalTestExecutionListener
期望在 Spring ApplicationContext
中定义PlatformTransactionManager
bean 进行测试。如果在测试的ApplicationContext
中有多个PlatformTransactionManager
实例,则可以使用@Transactional("myTxMgr")
或@Transactional(transactionManager = "myTxMgr")
声明限定符,或者TransactionManagementConfigurer
可以由@Configuration
class 实现。有关用于在测试ApplicationContext
中查找 transaction manager 的算法的详细信息,请参阅javadoc for TestContextTransactionUtils.retrieveTransactionManager()。
所有事物相关的Annotations的演示
以下基于 JUnit 4 的 example 显示了一个虚构的 integration 测试场景,它突出显示了所有 transaction-related 注释。 example 并非用于演示最佳实践,而是用于演示如何使用这些注释。有关更多信息和 configuration 示例,请参见annotation 支持部分。 Transaction management for @Sql包含一个额外的 example,它使用@Sql
进行声明性 SQL 脚本执行,并使用默认的 transaction 回滚语义。以下 example 以粗体显示相关注释:
1 |
|
在测试 ORM code 时避免 false 肯定
当您测试操作 Hibernate session 或 JPA 持久性 context 的 state 的 application code 时,请确保在 run 该 code 的测试方法中刷新基础工作单元。未能刷新基础工作单元可能会产生 false 肯定:您的测试通过,但相同的 code 会在实时的 production 环境中抛出 exception。请注意,这适用于维护 in-memory 工作单元的任何 ORM framework。在下面的 Hibernate-based example 测试用例中,一个方法演示 false 正面,另一个方法正确公开了刷新 session 的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 // ...
@Autowired
SessionFactory sessionFactory;
@Transactional
@Test // no expected exception!
public void falsePositive() {
updateEntityInHibernateSession();
// False positive: an exception will be thrown once the Hibernate
// Session is finally flushed (i.e., in production code)
}
@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
updateEntityInHibernateSession();
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
}
// ...以下 example 显示了 JPA 的匹配方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 // ...
EntityManager entityManager;
// no expected exception!
public void falsePositive() {
updateEntityInJpaPersistenceContext();
// False positive: an exception will be thrown once the JPA
// EntityManager is finally flushed (i.e., in production code)
}
public void updateWithEntityManagerFlush() {
updateEntityInJpaPersistenceContext();
// Manual flush is required to avoid false positive in test
entityManager.flush();
}
// ...执行SQL脚本
在针对关系数据库编写 integration 测试时,执行 SQL 脚本以将数据库 schema 或 insert 测试数据修改为表通常是有益的。 spring-jdbc
模块通过在加载 Spring ApplicationContext
时执行 SQL 脚本来支持初始化嵌入或现有数据库。有关详细信息,请参阅嵌入式数据库支持和使用嵌入式数据库测试数据访问逻辑。
虽然在加载ApplicationContext
时初始化数据库进行一次测试非常有用,但有时在 integration 测试期间能够修改数据库是很重要的。以下部分说明如何在 integration 测试期间以编程方式和声明方式执行 SQL 脚本。
以编程方式执行SQL脚本
Spring 提供了以下选项,用于在 integration 测试方法中以编程方式执行 SQL 脚本。
org.springframework.jdbc.datasource.init.ScriptUtils
org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils
提供了一组用于处理 SQL 脚本的静态实用程序方法,主要供 framework 内部使用。但是,如果您需要完全控制 SQL 脚本的解析和执行方式,ScriptUtils
可能比后面描述的其他一些替代方案更适合您的需求。有关详细信息,请参阅中中的各个方法。
ResourceDatabasePopulator
提供 object-based API,用于通过使用外部资源中定义的 SQL 脚本以编程方式填充,初始化或清理数据库。 ResourceDatabasePopulator
提供了用于配置解析和运行脚本时使用的字符编码,语句分隔符,comment 分隔符和错误处理标志的选项。每个 configuration 选项都有一个合理的默认值 value。有关默认值的详细信息,请参阅javadoc。要 run 在ResourceDatabasePopulator
中配置的脚本,您可以调用populate(Connection)
方法来执行针对java.sql.Connection
的填充程序或execute(DataSource)
方法来针对javax.sql.DataSource
执行填充程序。以下 example 指定测试 schema 和测试数据的 SQL 脚本,将语句分隔符设置为@@
,并针对DataSource
执行脚本:
1 |
|
请注意,ResourceDatabasePopulator
内部委托ScriptUtils
进行解析和 running SQL 脚本。同样,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)
方法在内部使用ResourceDatabasePopulator
到 run SQL 脚本。有关更多详细信息,请参阅 javadoc 以获取各种executeSqlScript(..)
方法。
以Sql注解声明性地执行SQL脚本
除了前面提到的以编程方式运行 SQL 脚本的机制之外,您还可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。具体来说,您可以在 test class 或 test 方法上声明@Sql
annotation,以将资源_path 配置为在 integration 测试方法之前或之后应该针对给定数据库 run 的 SQL 脚本。请注意,method-level 声明覆盖 class-level 声明,并且SqlScriptsTestExecutionListener
支持@Sql
,默认情况下启用。
路径资源语义
每个路径都被解释为 Spring Resource
。普通路径(对于 example,"schema.sql"
)被视为 classpath 资源,该资源相对于定义 test class 的包。以斜杠开头的路径被视为绝对 classpath 资源(对于 example,"/org/example/schema.sql"
)。使用指定的资源协议加载 references URL(对于 example,前缀为classpath:
,file:
,http:
的路径)的路径。
以下 example 显示了如何在 class level 和基于 JUnit Jupiter 的 integration test class 中的方法 level 中使用@Sql
:
1 |
|
默认脚本检测
如果未指定 SQL 脚本,则会尝试检测default
脚本,具体取决于声明@Sql
的位置。如果无法检测到默认值,则抛出IllegalStateException
。
- Class-level 声明:如果带注释的测试 class 为
com.example.MyTest
,则相应的默认脚本为classpath:com/example/MyTest.sql
。 - Method-level 声明:如果带注释的测试方法名为
testMethod()
并且在 classcom.example.MyTest
中定义,则相应的默认脚本为classpath:com/example/MyTest.testMethod.sql
。
声明多个Sql注解Sets
如果需要为给定的测试 class 或测试方法配置多个 SQL 集脚本,但具有不同的语法 configuration,不同的错误处理规则或每个集的不同执行阶段,则可以声明多个@Sql
实例。使用 Java 8,您可以使用@Sql
作为可重复的 annotation。否则,您可以使用@SqlGroup
annotation 作为显式容器来声明@Sql
的多个实例。
以下 example 显示了如何在 Java 8 中将@Sql
用作可重复的 annotation:
1 |
|
在前面的 example 中显示的场景中,test-schema.sql
脚本对 single-line comments 使用不同的语法。
以下 example 与前面的 example 相同,只是@Sql
声明在@SqlGroup
中组合在一起,以便与 Java 6 和 Java 7 兼容。
1 |
|
脚本执行阶段
默认情况下,SQL 脚本在相应的测试方法之前执行。但是,如果需要在测试方法之后运行一组特定的脚本(对于 example,以清理数据库 state),则可以使用@Sql
中的executionPhase
属性,如下面的 example 所示:
1 |
|
请注意,ISOLATED
和AFTER_TEST_METHOD
分别从Sql.TransactionMode
和Sql.ExecutionPhase
静态导入。
使用SqlConfig注解配置脚本
您可以使用@SqlConfig
annotation 配置脚本解析和错误处理。在 integration test class 上声明为 class-level annotation 时,@SqlConfig
用作 test class 层次结构中所有 SQL 脚本的 global configuration。当使用@Sql
annotation 的config
属性直接声明时,@SqlConfig
用作封闭@Sql
annotation 中声明的 SQL 脚本的本地 configuration。 @SqlConfig
中的每个属性都有一个隐式的默认值 value,它记录在相应属性的 javadoc 中。由于在 Java 语言规范中为 annotation 属性定义了规则,遗憾的是,不可能将null
的 value 赋给 annotation 属性。因此,为了支持继承的 global configuration 的覆盖,@SqlConfig
属性具有""
(对于 Strings)或DEFAULT
(对于枚举)的显式缺省 value。这种方法允许@SqlConfig
的局部声明通过提供除""
或DEFAULT
之外的 value 来有选择地覆盖@SqlConfig
的 global 声明中的各个属性。只要本地@SqlConfig
属性不提供""
或DEFAULT
以外的显式 value,就会继承 Global @SqlConfig
属性。因此,显式本地配置会覆盖 global configuration。
@Sql
和@SqlConfig
提供的 configuration 选项等同于ScriptUtils
和ResourceDatabasePopulator
支持的选项,但它们是<jdbc:initialize-database/>
XML 名称空间元素提供的选项的超集。有关详细信息,请参阅@Sql和@SqlConfig中各个属性的 javadoc。
事物管理@Sql
默认情况下,SqlScriptsTestExecutionListener
为使用@Sql
配置的脚本推断出所需的 transaction 语义。具体来说,SQL 脚本是 run 而没有 transaction,在现有的 Spring-managed transaction 中(对于 example,由TransactionalTestExecutionListener
管理的 transaction 用于使用@Transactional
注释的测试),或者在单独的 transaction 中,具体取决于@SqlConfig
中transactionMode
属性的配置 value 并且在测试的ApplicationContext
中存在PlatformTransactionManager
。但是,作为最低限度,测试的ApplicationContext
中必须存在javax.sql.DataSource
。
如果SqlScriptsTestExecutionListener
用于检测DataSource
和PlatformTransactionManager
并推断 transaction 语义的算法不符合您的需要,则可以通过设置@SqlConfig
的dataSource
和transactionManager
属性来指定显式名称。此外,您可以通过设置@SqlConfig
的transactionMode
属性来控制 transaction 传播行为(对于 example,脚本是否应该在隔离的 transaction 中运行)。虽然对@Sql
的 transaction management 的所有支持选项的详尽讨论超出了本 reference 手册的范围,但@SqlConfig和SqlScriptsTestExecutionListener的 javadoc 提供了详细信息,以下 example 显示了使用 JUnit Jupiter 和 transactional 测试的典型测试场景@Sql
:
1 |
|
请注意,在usersTest()
方法为 run 之后无需清理数据库,因为对数据库所做的任何更改(在测试方法内或在/test-data.sql
脚本中)都会被TransactionalTestExecutionListener
自动回滚(有关详细信息,请参阅transaction management) 。
使用@SqlMergeMode合并和重写配置
在Spring Framework 5.2中,可以将方法级的@Sql
声明与类级声明合并。例如,这允许您为每个测试类提供一次数据库模式或一些公共测试数据的配置,然后为每个测试方法提供附加的、特定于用例的测试数据。要启用@Sql
合并,请使用@SqlMergeMode(MERGE)
注释您的测试类或测试方法。要禁用特定测试方法(或特定测试子类)的合并,可以通过@SqlMergeMode(OVERRIDE)
切换回默认模式。查阅@SqlMergeMode注解文档小节以获得示例和更多细节。
Parallel测试执行
Spring Framework 5.0 引入了在使用 Spring TestContext Framework 时在单个 JVM 中执行 parallel 测试的基本支持。通常,这意味着大多数测试类或测试方法可以在 parallel 中执行,而无需对 test code 或 configuration 进行任何更改。
有关如何设置 parallel 测试执行的详细信息,请参阅测试 framework,build 工具或 IDE 的文档。
请记住,在测试套件中引入并发可能会导致意外的副作用,奇怪的运行时行为以及间歇性或看似随机失败的测试。因此,Spring 团队提供以下关于何时不在 parallel 中执行测试的一般指导原则。
如果测试,请不要在 parallel 中执行测试:
- 使用 Spring 的
@DirtiesContext
支持。 - 使用 JUnit 4 的
@FixMethodOrder
支持或任何测试 framework feature,旨在确保测试方法 run 在特定的 order 中。但请注意,如果在 parallel 中执行整个测试类,则不适用。 - 更改共享服务或系统的 state,例如数据库,消息 broker,文件系统等。这适用于 in-memory 和外部系统。
如果 parallel 测试执行失败,并且 exception 声明当前测试的
ApplicationContext
不再是 active,则这通常意味着ApplicationContext
已从另一个线程中的ContextCache
中删除。
这可能是由于使用@DirtiesContext
或由于ContextCache
的自动驱逐。如果@DirtiesContext
是罪魁祸首,您需要找到一种方法来避免使用@DirtiesContext
或从 parallel 执行中排除此类测试。如果已超出ContextCache
的最大大小,则可以增加高速缓存的最大大小。有关详细信息,请参阅context 缓存上的讨论。
Spring TestContext Framework 中的 Parallel 测试执行只有在底层
TestContext
implementation 提供复制构造函数时才有可能,如TestContext的 javadoc 中所述。 Spring 中使用的DefaultTestContext
提供了这样的构造函数。但是,如果使用提供自定义TestContext
implementation 的 third-party library,则需要验证它是否适合 parallel 测试执行。
TestContext框架支持Classes
本节介绍支持 Spring TestContext Framework 的各种 classes。
SpringJUnit4Runner
Spring TestContext Framework 通过自定义运行器(在 JUnit 4.12 或更高版本上支持)提供与 JUnit 4 的完整 integration。通过使用@RunWith(SpringJUnit4ClassRunner.class)
或更短的@RunWith(SpringRunner.class)
变体注释 test classes,开发人员可以实现标准的 JUnit 4-based 单元和 integration 测试,同时获得 TestContext framework 的好处,例如支持 loading application 上下文,依赖注入测试实例,transactional 测试方法执行, 等等。如果您想将 Spring TestContext Framework 与替代跑步者(例如 JUnit 4 的Parameterized
跑步者)或third-party跑步者(例如MockitoJUnitRunner
)一起使用,您可以选择使用Spring 支持 JUnit 规则。
以下 code 列表显示了使用自定义 Spring Runner
将 test class 配置为 run 的最低要求:
1 |
|
在前面的 example 中,@TestExecutionListeners
配置了一个空列表,以禁用默认的 listeners,否则需要通过@ContextConfiguration
配置ApplicationContext
。
SpringJunit4规则
org.springframework.test.context.junit4.rules
包提供以下 JUnit 4 规则(JUnit 4.12 或更高版本支持):
SpringClassRule
SpringMethodRule
SpringClassRule
是一个 JUnit TestRule
,它支持 Spring TestContext Framework 的 class-level features,而SpringMethodRule
是一个 JUnit MethodRule
,它支持 Spring TestContext Framework 的 instance-level 和 method-levelfeatures。
与SpringRunner
相比,Spring 的 rule-based JUnit 支持具有独立于任何org.junit.runner.Runner
implementation 的优点,因此可以与现有的替代运行程序(例如 JUnit 4 的Parameterized
)或 third-party 运行程序(例如MockitoJUnitRunner
)组合。
要支持 TestContext framework 的完整功能,必须将SpringClassRule
与SpringMethodRule
组合在一起。以下 example 显示了在 integration 测试中声明这些规则的正确方法:
1 | // Optionally specify a non-Spring Runner via @RunWith(...) |
JUnit4支持Classes
org.springframework.test.context.junit4
包为 JUnit 4-based 测试用例提供以下支持 classes(在 JUnit 4.12 或更高版本上受支持):
AbstractJUnit4SpringContextTests
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是一个抽象基础测试 class,它将 Spring TestContext Framework 与 JUnit 4 环境中的显式ApplicationContext
测试支持集成在一起。扩展AbstractJUnit4SpringContextTests
时,可以访问protected
applicationContext
实例变量,可用于执行显式 bean 查找或测试 context 的 state 作为整体。
AbstractTransactionalJUnit4SpringContextTests
是AbstractJUnit4SpringContextTests
的抽象 transactional 扩展,为 JDBC 访问添加了一些便利功能。这个 class 期望在ApplicationContext
中定义javax.sql.DataSource
bean 和PlatformTransactionManager
bean。当您扩展AbstractTransactionalJUnit4SpringContextTests
时,您可以访问protected
jdbcTemplate
实例变量,您可以使用该变量来运行 SQL statements 来查询数据库。您可以在 running database-related application code 之前和之后使用此类查询来确认数据库 state,并且 Spring 确保此类查询在与 application code 相同的 transaction 范围内运行。与 ORM 工具结合使用时,请务必避免使用误报。如JDBC 测试支持中所述,AbstractTransactionalJUnit4SpringContextTests
还提供了方便的方法,通过使用前面提到的jdbcTemplate
来委托JdbcTestUtils
中的方法。此外,AbstractTransactionalJUnit4SpringContextTests
提供了一个executeSqlScript(..)
方法,用于针对配置的DataSource
运行 SQL 脚本。
这些 classes 是扩展的便利。如果您不希望将 test classes 绑定到 Spring-specific class 层次结构,则可以使用
@RunWith(SpringRunner.class)
或Spring 的 JUnit 规则配置自己的自定义测试 classes。
JUnitJupiter的SpringExtension
Spring TestContext Framework 使用 JUnit 5 中引入的 JUnit Jupiter 测试 framework 提供完整的 integration。通过使用@ExtendWith(SpringExtension.class)
注释 test classes,您可以实现标准的 JUnit Jupiter-based 单元和 integration 测试,同时获得 TestContext framework 的好处,例如支持 loading application 上下文,依赖注入测试实例,transactional 测试方法执行等。
此外,由于 JUnit Jupiter 中的丰富扩展 API,Spring 可以在 Spring 支持 JUnit 4 和 TestNG 的 feature 集之上提供以下 features:
- 测试构造函数,测试方法和测试生命周期回调方法的依赖注入。有关详细信息,请参阅SpringExtension 的依赖注入。
- 基于 SpEL 表达式,环境变量,系统 properties 等对条件测试执行的强大支持。有关更多详细信息和示例,请参阅Spring JUnit Jupiter Testing Annotations中
@EnabledIf
和@DisabledIf
的文档。 - 自定义组合的注释,它们组合了来自 Spring 和 JUnit Jupiter 的 annotations。有关详细信息,请参阅Meta-Annotation 支持测试中的
@TransactionalDevTestConfig
和@TransactionalIntegrationTest
示例。
以下 code 列表显示了如何配置 test class 以将SpringExtension
与@ContextConfiguration
结合使用:
1 | // Instructs JUnit Jupiter to extend the test with Spring support. |
由于您也可以在 JUnit 5 中使用 annotations 作为 meta-annotations,Spring 可以提供@SpringJUnitConfig
和@SpringJUnitWebConfig
组合注释来简化测试ApplicationContext
和 JUnit Jupiter 的 configuration。
以下 example 使用@SpringJUnitConfig
来减少前一个 example 中使用的 configuration 数量:
1 | // Instructs Spring to register the SpringExtension with JUnit |
类似地,以下 example 使用@SpringJUnitWebConfig
创建WebApplicationContext
以与 JUnit Jupiter 一起使用:
1 | // Instructs Spring to register the SpringExtension with JUnit |
有关详细信息,请参阅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 测试 class 的构造函数中的参数类型为ApplicationContext
(或 sub-type)或带注释或带有@Autowired
,@Qualifier
或@Value
的 meta-annotated,则 Spring 会使用测试中的相应 bean 为该特定参数注入 value ApplicationContext
。如果所有参数都应由 Spring 提供,您还可以使用@Autowired
直接注释测试构造函数。
如果 test class 的构造函数本身用
@Autowired
注释,则 Spring 承担解析构造函数中所有参数的责任。因此,JUnit Jupiter 中没有注册的其他ParameterResolver
可以解析此类构造函数的参数。
在下面的示例中,Spring 将从TestConfig.class
加载的ApplicationContext
中的OrderService
bean 注入OrderServiceIntegrationTests
构造函数。
1 |
|
请注意,此 feature 允许测试依赖项为final
,因此不可变。
方法注入
如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数类型为ApplicationContext
(或 sub-type)或带注释或 meta-annotated 带有@Autowired
,@Qualifier
或@Value
,则 Spring 会使用相应的 bean 从该特定参数中注入 value 考试的ApplicationContext
。
在下面的示例中,Spring 将从TestConfig.class
加载的ApplicationContext
中的OrderService
注入到deleteOrder()
测试方法中:
1 |
|
由于 JUnit Jupiter 中ParameterResolver
支持的健壮性,您还可以将多个依赖项注入单个方法,不仅来自 Spring,还来自 JUnit Jupiter 本身或其他 third-party extensions。
以下 example 显示了如何同时将 Spring 和 JUnit Jupiter inject 依赖项同时包含在placeOrderRepeatedly()
测试方法中。
1 |
|
请注意,使用 JUnit Jupiter 中的@RepeatedTest
可以让测试方法获得对RepetitionInfo
的访问权限。
TestNG支持Classes
org.springframework.test.context.testng
包为基于 TestNG 的测试用例提供以下支持 classes:
AbstractTestNGSpringContextTests
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
是一个抽象基础测试 class,它将 Spring TestContext Framework 与 TestNG 环境中的显式ApplicationContext
测试支持集成在一起。扩展AbstractTestNGSpringContextTests
时,可以访问protected
applicationContext
实例变量,可用于执行显式 bean 查找或测试 context 的 state 作为整体。
AbstractTransactionalTestNGSpringContextTests
是AbstractTestNGSpringContextTests
的抽象 transactional 扩展,为 JDBC 访问添加了一些便利功能。这个 class 期望在ApplicationContext
中定义javax.sql.DataSource
bean 和PlatformTransactionManager
bean。扩展AbstractTransactionalTestNGSpringContextTests
时,可以访问可用于执行 SQL statements 以查询数据库的protected
jdbcTemplate
实例变量。您可以在 running database-related application code 之前和之后使用此类查询来确认数据库 state,并且 Spring 确保此类查询在与 application code 相同的 transaction 范围内运行。与 ORM 工具结合使用时,请务必避免使用误报。如JDBC 测试支持中所述,AbstractTransactionalTestNGSpringContextTests
还提供了方便的方法,通过使用前面提到的jdbcTemplate
来委托JdbcTestUtils
中的方法。此外,AbstractTransactionalTestNGSpringContextTests
提供了一个executeSqlScript(..)
方法,用于针对配置的DataSource
运行 SQL 脚本。
这些 classes 是扩展的便利。如果您不希望将 test classes 绑定到 Spring-specific class 层次结构,则可以使用
@ContextConfiguration
,@TestExecutionListeners
等配置自己的自定义测试 classes,并使用TestContextManager
手动检测 test class。有关如何检测测试 class 的示例,请参阅AbstractTestNGSpringContextTests
的 source code。
SpringMVC测试框架
Spring MVC Test framework 为使用 fluent API 测试 Spring MVC code 提供了第一个 class 支持,您可以将其与 JUnit,TestNG 或任何其他测试 framework 一起使用。它建立在spring-test
模块的Servlet API mock objects上,因此不使用 running Servlet 容器。它使用DispatcherServlet
提供完整的 Spring MVC 运行时行为,并且除了独立模式之外,还支持使用 TestContext framework 加载实际的 Spring configuration,在独立模式下,您可以手动实例化控制器并在 time 时测试它们。
Spring MVC Test 还为测试使用RestTemplate
的 code 提供 client-side 支持。 Client-side tests mock 服务器响应,也不使用 running 服务器。
Spring Boot 提供了一个选项来编写包含 running 服务器的完整的 end-to-end integration 测试。如果这是您的目标,请参阅Spring Boot reference 页面。有关 out-of-container 和 end-to-end integration 测试之间差异的更多信息,请参阅Out-of-Container 和 End-to-End Integration 测试之间的差异。
Server-Side测试
您可以使用 JUnit 或 TestNG 为 Spring MVC 控制器编写普通单元测试。为此,实例化控制器,使用模拟或存根依赖项对其进行注入,并调用其方法(根据需要传递MockHttpServletRequest
,MockHttpServletResponse
和其他)。但是,在编写这样的单元测试时,仍有许多未经测试:例如,请求映射,数据绑定,类型转换,验证等等。此外,还可以调用其他控制器方法(如@InitBinder
,@ModelAttribute
和@ExceptionHandler
)作为请求处理生命周期的一部分。
Spring MVC Test 的目标是通过执行请求并通过实际DispatcherServlet
生成响应来提供测试控制器的有效方法。
Spring MVC Test 建立在spring-test
模块中熟悉的“mock”实现 Servlet API上。这允许执行请求并生成响应,而无需在 Servlet 容器中运行 running。在大多数情况下,一切都应该像在运行时那样使用一些值得注意的 exceptions,如Out-of-Container 和 End-to-End Integration 测试之间的差异中所述。以下 JUnit Jupiter-based example 使用 Spring MVC Test:
1 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
上述测试依赖于 TestContext framework 的WebApplicationContext
支持,以从与 test class 相同的包中的 XML configuration 文件加载 Spring configuration,但也支持 Java-based 和 Groovy-based configuration。看到这些sample 测试。
MockMvc
实例用于对/accounts/1
执行GET
请求,并验证生成的响应是否为状态 200,content type 是application/json
,并且响应正文具有名为name
且带有 value Lee
的 JSON property。 Jayway JsonPath 项目支持jsonPath
语法。验证执行请求结果的许多其他选项将在本文档的后面部分讨论。
静态进口
来自前一节的 example 中的 fluent API 需要一些静态导入,例如MockMvcRequestBuilders.*
,MockMvcResultMatchers.*
和MockMvcBuilders.*
。查找这些 classes 的简单方法是搜索 match MockMvc*
的类型。如果您使用 Eclipse 或 Eclipse-based Spring 工具套件,请确保在 Java→编辑器→内容辅助→收藏夹下的 Eclipse 首选项中将它们添加为“最喜欢的静态成员”。这样做可以在 typing 静态方法 name 的第一个字符后使用内容辅助。其他 IDE(例如 IntelliJ)可能不需要任何其他 configuration。检查静态成员的 code 完成支持。
设置选择
您有两个主要选项来创建MockMvc
的实例。第一种是通过 TestContext framework 加载 Spring MVC configuration,它加载 Spring configuration 并将WebApplicationContext
注入测试以用于 build MockMvc
实例。以下 example 显示了如何执行此操作:
1 |
|
第二个选项是手动创建一个控制器实例,而不是 loading Spring configuration。相反,将自动创建与 MVC JavaConfig 或 MVC 名称空间大致相当的基本默认 configuration。您可以在一定程度上自定义它。以下 example 显示了如何执行此操作:
1 | public class MyWebTests { |
你应该使用哪种设置选项?
webAppContextSetup
加载您实际的 Spring MVC configuration,从而产生更完整的 integration 测试。由于 TestContext framework 缓存了加载的 Spring configuration,因此即使您在测试套件中引入了更多测试,它也有助于保持测试快速运行。此外,您可以通过 Spring configuration 将 mock 服务注入控制器,以继续专注于测试 web 层。以下 example 使用 Mockito 声明 mock 服务:
1 | <bean id="accountService" class="org.mockito.Mockito" factory-method="mock"> |
然后,您可以将 mock 服务注入测试以设置和验证您的期望,如下面的示例所示:
1 |
|
另一方面,standaloneSetup
更接近单元测试。它在 time 测试一个控制器。您可以使用 mock 依赖项手动 inject 控制器,它不涉及 loading Spring configuration。这些测试更侧重于样式,并且更容易看到正在测试哪个控制器,是否需要任何特定的 Spring MVC configuration 才能工作,等等。 standaloneSetup
也是编写 ad-hoc 测试以验证特定行为或调试问题的一种非常方便的方法。
与大多数“整合与单元测试”辩论一样,没有正确或错误的答案。但是,使用standaloneSetup
确实意味着需要在 order 中进行额外的webAppContextSetup
测试以验证您的 Spring MVC configuration。或者,您可以使用webAppContextSetup
编写所有测试,在 order 中始终针对您的实际 Spring MVC configuration 进行测试。
设置Features
无论您使用哪个 MockMvc 构建器,所有MockMvcBuilder
implementations 都提供了一些 common 和非常有用的 features。对于 example,您可以为所有请求声明Accept
标头,并期望在所有响应中状态为 200 以及Content-Type
标头,如下所示:
1 | // static import of MockMvcBuilders.standaloneSetup |
另外,third-party frameworks(和 applications)可以 pre-package 设置指令,例如MockMvcConfigurer
中的指令。 Spring Framework 有一个这样的 built-in implementation,它有助于跨请求保存和 re-use HTTP session。您可以按如下方式使用它:
1 | // static import of SharedHttpSessionConfigurer.sharedHttpSession |
有关ConfigurableMockMvcBuilder的 javadoc,请参阅所有 MockMvc 构建器 features 的列表,或使用 IDE 浏览可用选项。
执行请求
您可以执行使用任何 HTTP 方法的请求,如以下 example 所示:
1 | mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); |
您还可以执行内部使用MockMultipartHttpServletRequest
的文件上载请求,以便不会实际解析 multipart 请求。相反,您必须将其设置为类似于以下 example:
1 | mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); |
您可以在 URI 模板样式中指定查询参数,如以下 example 所示:
1 | mockMvc.perform(get("/hotels?thing={thing}", "somewhere")); |
您还可以添加表示查询或表单参数的 Servlet 请求参数,如以下 example 所示:
1 | mockMvc.perform(get("/hotels").param("thing", "somewhere")); |
如果 application code 依赖于 Servlet 请求参数,并且没有显式检查查询 string(最常见的情况),那么使用哪个选项并不重要。但请记住,对 URI 模板提供的查询参数进行解码,而通过param(…)
方法提供的请求参数预计已经被解码。
在大多数情况下,最好将 context 路径和 Servlet 路径保留在请求 URI 之外。如果必须使用完整请求 URI 进行测试,请确保相应地设置contextPath
和servletPath
以使请求映射有效,如下面的 example 所示:
1 | mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) |
在前面的示例中,对每个执行的请求设置contextPath
和servletPath
将是麻烦的。相反,您可以设置默认请求 properties,如下面的 example 所示:
1 | public class MyWebTests { |
前面的 properties 会影响通过MockMvc
实例执行的每个请求。如果在给定请求上也指定了相同的 property,它将覆盖默认的 value。这就是默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为必须在每个请求中指定它们。
定义期望
您可以在执行请求后通过附加一个或多个.andExpect(..)
_call 来定义期望,如下面的 example 所示:
1 | mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); |
MockMvcResultMatchers.*
提供了许多期望,其中一些期望与更详细的期望进一步嵌套。
期望分为两大类。第一类断言验证响应的 properties(例如,响应状态,headers 和内容)。这些是断言最重要的结果。
第二类断言超出了回应范围。这些断言允许您检查 Spring MVC 特定方面,例如处理请求的控制器方法,是否引发和处理 exception,model 的内容是什么,选择了哪个视图,添加了哪些 flash 属性等等。它们还允许您检查 Servlet 特定方面,例如 request 和 session 属性。
以下测试断言 binding 或验证失败:
1 | mockMvc.perform(post("/persons")) |
很多时候,在编写测试时,转储执行的请求的结果很有用。您可以按如下方式执行此操作,其中print()
是来自MockMvcResultHandlers
的静态 import:
1 | mockMvc.perform(post("/persons")) |
由于 long 因为请求处理不会导致未处理的 exception,print()
方法会将所有可用的结果数据打印到System.out
。 Spring Framework 4.2 引入了log()
方法和print()
方法的另外两个变体,一个接受OutputStream
,另一个接受Writer
。对于 example,调用print(System.err)
将结果数据打印到System.err
,而调用print(myWriter)
则将结果数据打印到自定义 writer。如果要记录结果数据而不是打印结果数据,可以调用log()
方法,该方法将结果数据记录为org.springframework.test.web.servlet.result
logging 类别下的单个DEBUG
消息。
在某些情况下,您可能希望直接访问结果并验证无法验证的内容。这可以通过在所有其他期望之后附加.andReturn()
来实现,如下面的示例所示:
1 | MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); |
如果所有测试重复相同的期望,则可以在 building MockMvc
实例时设置 common 期望值,如下面的 example 所示:
1 | standaloneSetup(new SimpleController()) |
请注意,如果没有 creating 单独的MockMvc
实例,则始终会应用 common 期望值并且无法覆盖。
当 JSON 响应内容包含使用Spring HATEOAS创建的超媒体链接时,您可以使用 JsonPath 表达式验证生成的链接,如下面的 example 所示:
1 | mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) |
当 XML 响应内容包含使用Spring HATEOAS创建的超媒体链接时,您可以使用 XPath 表达式验证生成的链接:
1 | Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); |
过滤器注册
设置MockMvc
实例时,可以注册一个或多个 Servlet Filter
实例,如下面的 example 所示:
1 | mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); |
已注册的过滤器通过spring-test
从spring-test
调用,最后一个过滤器委托给DispatcherServlet
。
Out-of-Container和End-to-End集成测试之间的差异
如前所述 Spring MVC Test 构建在spring-test
模块的 Servlet API mock objects 之上,并且不使用 running Servlet 容器。因此,与使用实际 client 和 server running 的完整 end-to-end integration 测试相比,存在一些重要差异。
考虑这一点的最简单方法是从空白MockHttpServletRequest
开始。无论你添加什么是请求变成什么。令你惊讶的是,默认情况下没有 context 路径;没有jsessionid
cookie;没有转发,错误或异步调度;因此,没有实际的 JSP 渲染。相反,“转发”和“重定向”的 URL 保存在MockHttpServletResponse
中,并且可以满足期望。
这意味着,如果使用 JSP,则可以验证请求转发到的 JSP 页面,但不呈现 HTML。换句话说,不调用 JSP。但请注意,所有其他不依赖转发的呈现技术(如 Thymeleaf 和 Freemarker)都会按预期将 HTML 呈现给响应主体。通过@ResponseBody
方法呈现 JSON,XML 和其他格式也是如此。
或者,您可以考虑 Spring Boot 与@WebIntegrationTest
的完整 end-to-end integration 测试支持。见Spring Boot Reference Guide。
每种方法都有利弊。 Spring MVC Test 中提供的选项在从经典单元测试到完整整合测试的规模上是不同的停止。确切地说,Spring MVC Test 中的所有选项都属于经典单元测试类别,但它们更接近它。例如,您可以通过将模拟服务注入控制器来隔离 web 层,在这种情况下,您只通过DispatcherServlet
测试 web 层,但使用实际的 Spring configuration,因为您可以独立于上面的层测试数据访问层。此外,您可以使用 stand-alone 设置,在 time 时关注一个控制器并手动提供使其工作所需的 configuration。
使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的测试是 server-side,所以你可以检查使用了什么处理程序,是否使用 HandlerExceptionResolver 处理 exception,model 的内容是什么,binding 错误是什么等细节。这意味着编写期望更容易,因为服务器不是黑盒子,就像通过实际的 HTTP client 测试它一样。这通常是经典单元测试的一个优点:它更容易编写,推理和调试,但不能取代完整整合测试的需要。在同一时间,重要的是不要忽视响应是最重要的检查事实。简而言之,即使在同一个项目中,也存在多种样式和测试策略的空间。
进一步Server-Side测试实例
framework 自己的测试包括许多 sample 测试旨在展示如何使用 Spring MVC Test。您可以浏览这些示例以获取更多想法。此外,spring-mvc-showcase项目具有基于 Spring MVC 测试的完整测试覆盖率。
HtmlUnit集成
Spring 在MockMvc和HtmlUnit之间提供 integration。这简化了使用 HTML-based 视图时执行 end-to-end 测试的过程。这个 integration 让你:
- 使用HtmlUnit,webdriver和创业板等工具轻松测试 HTML 页面,而无需部署到 Servlet 容器。
- 在页面中测试 JavaScript。
- (可选)使用 mock 服务进行测试以加快测试速度。
- 在 in-container end-to-end 测试和 out-of-container integration 测试之间共享逻辑。
MockMvc 使用不依赖于 Servlet 容器的模板技术(对于 example,Thymeleaf,FreeMarker 等),但它不适用于 JSP,因为它们依赖于 Servlet 容器。
为什么HtmlUnit集成?
想到的最明显的问题是“为什么我需要这个?”通过探索一个非常基本的 sample application 可以找到答案。假设您有 Spring MVC web application,它支持Message
object 上的 CRUD 操作。 application 还支持对所有消息进行分页。你会怎么做测试呢?
使用 Spring MVC Test,我们可以轻松测试是否能够创建Message
,如下所示:
1 | MockHttpServletRequestBuilder createMessage = post("/messages/") |
如果我们想测试允许我们创建消息的表单视图,该怎么办?对于 example,假设我们的表单看起来像以下代码段:
1 | <form id="messageForm" action="/messages/" method="post"> |
我们如何确保我们的表单产生正确的请求以创建新消息?天真的尝试可能类似于以下内容:
1 | mockMvc.perform(get("/messages/form")) |
该测试有一些明显的缺点。如果我们更新控制器以使用参数message
而不是text
,我们的表单测试将继续通过,即使 HTML 表单与控制器不同步。要解决这个问题,我们可以结合两个测试,如下所示:
1 | String summaryParamName = "summary"; |
这样可以降低我们的测试错误传递的风险,但仍然存在一些问题:
- 如果我们的页面上有多个表单怎么办?不可否认,我们可以更新我们的 XPath 表达式,但是由于我们考虑了更多因素,它们变得更加复杂:字段是否是正确的类型?字段是否已启用?等等。
- 另一个问题是我们正在做我们期望的工作。我们必须首先验证视图,然后我们使用我们刚刚验证的相同参数提交视图。理想情况下,这可以一次完成。
- 最后,我们仍然无法解释一些事情。例如,如果表单具有我们希望测试的 JavaScript 验证,该怎么办?
总体问题是测试 web 页面不涉及单个交互。相反,它是用户如何与 web 页面交互以及 web 页面如何与其他资源交互的组合。对于 example,表单视图的结果用作用户输入创建消息的输入。此外,我们的表单视图可能会使用影响页面行为的其他资源,例如 JavaScript 验证。
Integration测试救援?
要解决前面提到的问题,我们可以执行 end-to-end integration 测试,但这有一些缺点。考虑测试让我们浏览消息的视图。我们可能需要以下测试:
- 我们的页面是否向用户显示通知,指示消息为空时没有结果可用?
- 我们的页面是否正确显示单个消息?
- 我们的页面是否正确支持分页?
要设置这些测试,我们需要确保我们的数据库包含正确的消息。这导致了许多额外的挑战:
- 确保数据库中存在正确的消息可能很繁琐。 (考虑外国 key constraints.)
- 测试可能会变慢,因为每个测试都需要确保数据库处于正确的 state 状态。
- 由于我们的数据库需要在特定的 state 中,因此我们无法在 parallel 中运行测试。
- 对诸如 auto-generatedid,时间戳等项目执行断言可能很困难。
这些挑战并不意味着我们应该完全放弃 end-to-end 整合测试。相反,我们可以通过重构我们的详细测试来减少 end-to-end 整合测试的数量,以便使用 mock 服务,这些服务运行速度更快,更可靠,没有副作用。然后,我们可以实现少量 true end-to-end integration 测试,以验证简单的工作流程,以确保一切正常工作。
输入HtmlUnit集成
那么我们如何才能在测试页面交互之间取得平衡,并在测试套件中保持良好的性能?答案是:“通过将 MockMvc 与 HtmlUnit 集成。”
HtmlUnit集成选项
当您想要将 MockMvc 与 HtmlUnit 集成时,您有许多选项:
- MockMvc 和 HtmlUnit:如果要使用原始 HtmlUnit libraries,请使用此选项。
- MockMvc 和 WebDriver:使用此选项可以简化 integration 和 end-to-end 测试之间的开发和重用 code。
- MockMvc 和 Geb:如果要使用 Groovy 进行测试,简化开发并在 integration 和 end-to-end testing 之间重用 code,请使用此选项。
MockMvc和HtmlUnit
本节介绍如何集成 MockMvc 和 HtmlUnit。如果要使用原始 HtmlUnit libraries,请使用此选项。
MockMvc和HtmlUnit设置
首先,确保您已在net.sourceforge.htmlunit:htmlunit
中包含测试依赖项。为了使用 HtmlUnit 和 Apache HttpComponents 4.5,你需要使用 HtmlUnit 2.18 或更高版本。
我们可以使用MockMvcWebClientBuilder
轻松创建一个与 MockMvc 集成的 HtmlUnit WebClient
,如下所示:
1 |
|
这是使用
MockMvcWebClientBuilder
的简单示例。有关高级用法,请参阅高级 MockMvcWebClientBuilder。
这确保了 references localhost
作为服务器的任何 URL 都定向到我们的MockMvc
实例,而无需真正的 HTTP 连接。正常情况下,使用网络连接请求任何其他 URL。这让我们可以轻松测试 CDN 的使用。
MockMvc和HtmlUnit用法
现在我们可以像往常一样使用 HtmlUnit,但不需要将 application 部署到 Servlet 容器。对于 example,我们可以请求视图创建包含以下内容的消息:
1 | HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); |
默认的 context 路径是
""
。或者,我们可以指定 context 路径,如高级 MockMvcWebClientBuilder中所述。
一旦我们对HtmlPage
进行了 reference,我们就可以填写表单并提交它以创建一条消息,如下面的 example 所示:
1 | HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); |
最后,我们可以验证是否已成功创建新消息。以下断言使用AssertJ library:
1 | assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); |
前面的 code 以多种方式改进了我们的MockMvc 测试。首先,我们不再需要显式验证我们的表单,然后创建一个看起来像表单的请求。相反,我们请求表单,填写表单并提交表单,从而显着减少开销。
另一个重要因素是HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们还可以在页面中测试 JavaScript 的行为。
有关使用 HtmlUnit 的其他信息,请参阅HtmlUnit 文档。
高级MockMvcWebClientBuilder
在到目前为止的示例中,我们以最简单的方式使用MockMvcWebClientBuilder
,通过基于 Spring TestContext Framework 为我们加载的WebApplicationContext
构建WebClient
。在以下 example 中重复此方法:
1 |
|
我们还可以指定其他 configuration 选项,如下面的 example 所示:
1 | WebClient webClient; |
作为替代方案,我们可以通过单独配置MockMvc
实例并将其提供给MockMvcWebClientBuilder
来执行完全相同的设置,如下所示:
1 | MockMvc mockMvc = MockMvcBuilders |
这更加冗长,但是,通过使用MockMvc
实例构建WebClient
,我们可以轻而易举地获得 MockMvc 的全部功能。
有关创建
MockMvc
实例的其他信息,请参阅设置选择。
MockMvc和WebDriver
在前面的部分中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API 结合使用。在本节中,我们使用 Selenium webdriver中的其他抽象来使事情变得更加容易。
为什么选择WebDriver和MockMvc?
我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver 呢? Selenium WebDriver 提供了一个非常优雅的 API,可以让我们轻松地组织我们的 code。为了更好地展示它的工作原理,我们将在本节中探讨一个 example。
尽管是Selenium的一部分,但 WebDriver 并不需要 Selenium Server 来运行您的测试。
假设我们需要确保正确创建消息。测试涉及查找 HTML 表单输入元素,填写它们以及进行各种断言。
这种方法导致许多单独的测试,因为我们也想测试错误条件。例如,如果我们只填写表单的一部分,我们希望确保收到错误。如果我们填写整个表格,则应在之后显示新创建的消息。
如果其中一个字段被命名为“summary”,我们可能会在我们的测试中有多个类似于以下重复的内容:
1 | HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); |
那么如果我们将id
更改为smmry
会发生什么?这样做会迫使我们更新所有测试以包含此更改。这违反了 DRY 原则,因此我们理想情况下应将此 code 提取到自己的方法中,如下所示:
1 | public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { |
这样做可确保我们在更改 UI 时不必更新所有测试。
我们甚至可以进一步将这个逻辑放在一个Object
中,代表我们当前所在的HtmlPage
,如下面的 example 所示:
1 | public class CreateMessagePage { |
以前,这个 pattern 被称为Page Object Pattern。虽然我们当然可以使用 HtmlUnit 执行此操作,但 WebDriver 提供了一些我们在以下部分中探讨的工具,以使此 pattern 更容易实现。
MockMvc和WebDriver设置
要将 Selenium WebDriver 与 Spring MVC Test framework 一起使用,请确保您的项目包含对org.seleniumhq.selenium:selenium-htmlunit-driver
的测试依赖项。
我们可以使用MockMvcHtmlUnitDriverBuilder
轻松创建与 MockMvc 集成的 Selenium WebDriver,如以下 example 所示:
1 |
|
这是使用
MockMvcHtmlUnitDriverBuilder
的简单示例。有关更高级的用法,请参阅高级 MockMvcHtmlUnitDriverBuilder。
前面的 example 确保将_re 作为服务器的任何 URL 定向到我们的MockMvc
实例,而无需真正的 HTTP 连接。正常情况下,使用网络连接请求任何其他 URL。这让我们可以轻松测试 CDN 的使用。
MockMvc和WebDriver用法
现在我们可以像往常一样使用 WebDriver,但不需要将 application 部署到 Servlet 容器。对于 example,我们可以请求视图创建包含以下内容的消息:
1 | CreateMessagePage page = CreateMessagePage.to(driver); |
然后我们可以填写表单并提交它以创建消息,如下所示:
1 | ViewMessagePage viewMessagePage = |
这通过利用 Page Object Pattern 改进了HtmlUnit 测试的设计。正如我们在为什么选择 WebDriver 和 MockMvc?中提到的,我们可以使用 Page Object Pattern 和 HtmlUnit,但使用 WebDriver 会更容易。考虑以下CreateMessagePage
implementation:
1 | public class CreateMessagePage |
1 | CreateMessagePage 扩展AbstractPage 。我们不会详细介绍AbstractPage 的细节,但总的来说,它包含了我们所有页面的 common 功能。例如,如果我们的 application 具有导航栏,global 错误消息和其他 features,我们可以将此逻辑放在共享位置。 |
2 | 我们有一个成员变量,用于我们感兴趣的 HTML 页面的每个部分。这些是WebElement 类型。 WebDriver 的PageFactory允许我们通过自动解析每个WebElement 从CreateMessagePage 的 HtmlUnit version 中删除大量 code。 PageFactory#initElements(WebDriver,Class)方法通过使用字段 name 自动解析每个WebElement ,并通过 HTML 页面中元素的id 或name 查找它。 |
3 | 我们可以使用@FindBy annotation来覆盖默认的查找行为。我们的 example 显示了如何使用@FindBy annotation 查找带有css 选择器的提交按钮(输入[2083])。 |
最后,我们可以验证是否已成功创建新消息。以下断言使用AssertJ断言 library:
1 | assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); |
我们可以看到我们的ViewMessagePage
允许我们与自定义域 model 进行交互。对于 example,它公开了一个返回Message
object 的方法:
1 | public Message getMessage() throws ParseException { |
然后我们可以在断言中使用富域 objects。
最后,我们不要忘记在测试完成时关闭WebDriver
实例,如下所示:
1 |
|
有关使用 WebDriver 的其他信息,请参阅 Selenium WebDriver 文档。
高级MockMvcHtmlUnitDriverBuilder
在到目前为止的示例中,我们以最简单的方式使用MockMvcHtmlUnitDriverBuilder
,通过基于 Spring TestContext Framework 为我们加载的WebApplicationContext
构建WebDriver
。这里重复这种方法,如下:
1 |
|
我们还可以指定其他 configuration 选项,如下所示:
1 | WebDriver driver; |
作为替代方案,我们可以通过单独配置MockMvc
实例并将其提供给MockMvcHtmlUnitDriverBuilder
来执行完全相同的设置,如下所示:
1 | MockMvc mockMvc = MockMvcBuilders |
这更加冗长,但是,通过使用MockMvc
实例构建WebDriver
,我们可以轻而易举地获得 MockMvc 的全部功能。
有关创建
MockMvc
实例的其他信息,请参阅设置选择。
MockMvc和Geb
在上一节中,我们了解了如何将 MockMvc 与 WebDriver 一起使用。在本节中,我们使用创业板使我们的测试甚至 Groovy-er。
为什么选择Geb和MockMvc?
Geb 由 WebDriver 支持,因此它提供了许多我们从 WebDriver 获得的同样的好处。但是,通过为我们处理一些样板 code,Geb 使事情变得更加容易。
MockMvc和Geb设置
我们可以使用使用 MockMvc 的 Selenium WebDriver 轻松初始化 Geb Browser
,如下所示:
1 | def setup() { |
这是使用
MockMvcHtmlUnitDriverBuilder
的简单示例。有关更高级的用法,请参阅高级 MockMvcHtmlUnitDriverBuilder。
这可确保任何引用localhost
作为服务器的 URL 都定向到我们的MockMvc
实例,而无需真正的 HTTP 连接。正常使用网络连接请求任何其他 URL。这让我们可以轻松测试 CDN 的使用。
MockMvc和Geb用法
现在我们可以像往常一样使用 Geb,但不需要将 application 部署到 Servlet 容器。对于 example,我们可以请求视图创建包含以下内容的消息:
1 | to CreateMessagePage |
然后我们可以填写表单并提交它以创建消息,如下所示:
1 | when: |
任何未识别的方法 calls 或 property 访问或未找到的 references 将被转发到当前页面 object。这删除了直接使用 WebDriver 时我们需要的许多样板 code。
与直接使用 WebDriver 一样,这通过使用 Page Object Pattern 改进了HtmlUnit 测试的设计。如前所述,我们可以使用 Page Object Pattern 和 HtmlUnit 以及 WebDriver,但使用 Geb 更容易。考虑我们新的 Groovy-based CreateMessagePage
implementation:
1 | class CreateMessagePage extends Page { |
我们的CreateMessagePage
扩展Page
。我们不会详细介绍Page
的细节,但总的来说,它包含了我们所有页面的 common 功能。我们定义了一个可以在其中找到此页面的 URL。这让我们可以导航到页面,如下所示:
1 | to CreateMessagePage |
我们还有一个at
闭包,用于确定我们是否在指定页面。如果我们在正确的页面上,它应该_ret。这就是为什么我们可以断言我们在正确的页面上,如下所示:
1 | then: |
我们在闭包中使用断言,以便在错误的页面中我们可以确定出错的地方。
接下来,我们创建一个content
闭包,指定页面中所有感兴趣的区域。我们可以使用jQuery-ish Navigator API来选择我们感兴趣的内容。
最后,我们可以验证是否已成功创建新消息,如下所示:
1 | then: |
有关如何充分利用 Geb 的更多详细信息,请参阅The Geb用户手册。
客户端REST测试
您可以使用 client-side 测试来测试内部使用RestTemplate
的 code。 idea 用于声明预期的请求并提供“存根”响应,以便您可以专注于单独测试 code(即,无需运行服务器)。以下 example 显示了如何执行此操作:
1 | RestTemplate restTemplate = new RestTemplate(); |
在前面的示例中,MockRestServiceServer
(client-side REST 测试的中央 class)使用自定义ClientHttpRequestFactory
配置RestTemplate
,该自定义ClientHttpRequestFactory
根据预期断言实际请求并返回“存根”响应。在这种情况下,我们期望/greeting
的请求并且想要返回带有text/plain
内容的 200 响应。我们可以根据需要定义其他预期请求和存根响应。当我们定义期望的请求和存根响应时,RestTemplate
可以像往常一样在 client-side code 中使用。在测试结束时,mockServer.verify()
可用于验证是否已满足所有期望。
默认情况下,请求在声明期望的 order 中。您可以在 building 服务器时设置ignoreExpectOrder
选项,在这种情况下,检查所有期望(在 order 中)以查找给定请求的 match。这意味着允许请求进入任何 order。以下 example 使用ignoreExpectOrder
:
1 | server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); |
即使默认情况下无序请求,每个请求也只允许执行一次。 expect
方法提供了一个重载变量,它接受一个指定计数范围的ExpectedCount
参数(对于 example,once
,manyTimes
,max
,min
,between
等)。以下 example 使用times
:
1 | RestTemplate restTemplate = new RestTemplate(); |
请注意,如果未设置ignoreExpectOrder
(默认值),并且因此请求在 order 声明中,则该 order 仅适用于任何预期请求中的第一个。对于 example,如果预期“/something”两次,然后是“/somewhere”三次,那么在请求“/somewhere”之前应该有“/something”的请求,但是,除了后续的“/something”和“/somewhere”之外“,请求可以在任何时间到来。
作为上述所有选项的替代方案,client-side 测试支持还提供ClientHttpRequestFactory
implementation,您可以将其配置为RestTemplate
以将其绑定到MockMvc
实例。这允许使用实际 server-side 逻辑处理请求但不运行服务器。以下 example 显示了如何执行此操作:
1 | MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); |
静态进口
与 server-side 测试一样,client-side 测试的 fluent API 需要一些静态导入。通过搜索MockRest*
很容易找到。 Eclipse 用户应该在 Java→编辑器→内容辅助→收藏夹下的 Eclipse 首选项中添加MockRestRequestMatchers.*
和MockRestResponseCreators.*
作为“最喜欢的静态成员”。这允许在 typing 静态方法 name 的第一个字符后使用内容辅助。其他 IDE(例如 IntelliJ)可能不需要任何额外的 configuration。检查静态成员对 code 完成的支持。
客户端REST测试的更多示例
Spring MVC Test 自己的测试包括example 测试 client-side REST 测试。
WebTestClient
WebTestClient
是一个围绕Web 客户端的瘦 shell,用它来执行请求并公开一个专用的,流畅的 API 来验证响应。 WebTestClient
使用mock 请求和响应绑定到 WebFlux application,或者它可以通过 HTTP 连接测试任何 web 服务器。
Kotlin 用户:请参阅这个部分与使用
WebTestClient
相关。
建立
要创建WebTestClient
,您必须选择多个服务器设置选项之一。实际上,您要么将 WebFlux application 配置为绑定,要么使用 URL 连接到运行服务器。
绑定到控制器
以下 example 显示了如何创建服务器设置以在 time 测试一个@Controller
:
1 | client = WebTestClient.bindToController(new TestController()).build(); |
前面的 example 加载WebFlux Java configuration并注册给定的控制器。通过使用 mock 请求和响应 objects,在没有 HTTP 服务器的情况下测试生成的 WebFlux application。构建器上有更多方法可以自定义默认的 WebFlux Java configuration。
绑定到RouterFunction
以下 example 显示了如何从RouterFunction设置服务器:
1 | RouterFunction<?> route = ... |
在内部,configuration 传递给RouterFunctions.toWebHandler
。通过使用 mock 请求和响应 objects,在没有 HTTP 服务器的情况下测试生成的 WebFlux application。
绑定到ApplicationContext
以下 example 显示了如何从 application 的 Spring configuration 或其某个子集设置服务器:
1 |
|
1 | 指定要加载的 configuration |
2 | Inject the configuration |
3 | 创建WebTestClient |
在内部,configuration 被传递给WebHttpHandlerBuilder
以设置请求处理链。有关详细信息,请参阅WebHandler API。通过使用 mock 请求和响应 objects,在没有 HTTP 服务器的情况下测试生成的 WebFlux application。
绑定到服务器
以下服务器设置选项允许您连接到运行服务器:
1 | client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build(); |
ClientBuilder
除了前面描述的服务器设置选项之外,您还可以配置 client 选项,包括基本 URL,默认 headers,client 过滤器等。这些选项在bindToServer
之后随时可用。对于所有其他人,您需要使用configureClient()
从服务器转换到 client configuration,如下所示:
1 | client = WebTestClient.bindToController(new TestController()) |
写测试
WebTestClient
提供与Web 客户端相同的 API,直到使用exchange()
执行请求为止。 exchange()
之后的内容是用于验证响应的链式 API 工作流程。
通常,您首先声明响应状态和 headers,如下所示:
1 | client.get().uri("/persons/1") |
然后指定如何解码和使用响应正文:
expectBody(Class<T>)
:解码为单个 object。expectBodyList(Class<T>)
:将 objects 解码并收集到List<T>
。expectBody()
:为JSON 内容或空体解码为byte[]
。
然后你可以使用 built-in 断言为身体。以下 example 显示了一种方法:
1 | client.get().uri("/persons") |
您还可以超越 built-in 断言并创建自己的断言,如下面的示例所示:
1 | client.get().uri("/persons/1") |
您也可以退出工作流程并获得结果,如下所示:
1 | EntityExchangeResult<Person> result = client.get().uri("/persons/1") |
当您需要使用泛型解码为目标类型时,请查找接受ParameterizedTypeReference而不是
Class<T>
的重载方法。
没有内容
如果响应没有内容(或者您不关心它),请使用Void.class
,这可确保释放资源。以下 example 显示了如何执行此操作:
1 | client.get().uri("/persons/123") |
或者,如果要断言没有响应内容,可以使用类似于以下内容的 code:
1 | client.post().uri("/persons") |
JSON内容
使用expectBody()
时,响应将作为byte[]
使用。这对原始内容断言很有用。对于 example,您可以使用JSONAssert来验证 JSON 内容,如下所示:
1 | client.get().uri("/persons/1") |
您还可以使用JSONPath表达式,如下所示:
1 | client.get().uri("/persons") |
Streaming响应
要测试无限流(对于 example,"text/event-stream"
或"application/stream+json"
),您需要在响应状态和标头断言之后立即退出链式 API(使用returnResult
),如下面的 example 所示:
1 | FluxExchangeResult<MyEvent> result = client.get().uri("/events") |
现在你可以使用Flux<T>
,断言解码后的 objects,然后在满足测试目标时取消。我们建议使用reactor-test
模块中的StepVerifier
来执行此操作,如下面的 example 所示:
1 | Flux<Event> eventFux = result.getResponseBody(); |
请求正文
当涉及 building 请求时,WebTestClient
提供与WebClient
相同的 API,implementation 主要是一个简单的 pass-through。有关如何使用正文准备请求的示例,请参阅WebClient 文档,包括提交表单数据,Multipart 请求等。
PetClinic例子
在GitHub 上上可用的 PetClinic application 显示了 JUnit 4 环境中 Spring TestContext Framework 的几个 features。大多数测试功能都包含在AbstractClinicTests
中,其中部分列表如下:
1 | import static org.junit.Assert.assertEquals; |
1 | 从默认位置加载 application context:AbstractClinicTests-context.xml 。 |
2 | 这个测试用例扩展了AbstractTransactionalJUnit4SpringContextTests class,它继承了依赖注入的 configuration(通过DependencyInjectionTestExecutionListener )和 transactional 行为(通过TransactionalTestExecutionListener )。 |
3 | clinic 实例变量(正在测试的 application object)由依赖注入通过@Autowired 语义设置。 |
4 | getVets() 方法显示了如何使用继承的countRowsInTable() 方法轻松验证给定 table 中的行数,从而验证正在测试的 application code 的正确行为。这允许更强的测试并减少对精确测试数据的依赖性。例如,您可以在数据库中添加其他行而不会破坏测试。 |
与使用数据库的许多 integration 测试一样,AbstractClinicTests
中的大多数测试依赖于测试用例 run 之前数据库中已存在的最小数据量。或者,您可以在测试用例的测试夹具设置中填充数据库(同样,在与测试相同的 transaction 中)。
PetClinic application 支持三种数据访问技术:JDBC,Hibernate 和 JPA。通过在没有任何特定资源位置的情况下声明@ContextConfiguration
,AbstractClinicTests
class 从默认位置AbstractClinicTests-context.xml
加载了 application context,它声明 common DataSource
。子类指定必须声明PlatformTransactionManager
的其他 context 位置和Clinic
的具体 implementation。
例如,PetClinic 测试的 Hibernate implementation 包含以下 implementation。对于此 example,HibernateClinicTests
不包含 code 的 line。我们只需声明@ContextConfiguration
,测试继承自AbstractClinicTests
。因为声明@ContextConfiguration
没有任何特定的资源位置,Spring TestContext Framework 从AbstractClinicTests-context.xml
中定义的所有 beans(即继承的位置)和HibernateClinicTests-context.xml
加载 application context,可能覆盖AbstractClinicTests-context.xml
中定义的 beans。以下清单显示了HibernateClinicTests
class 的定义:
1 | 1) ( |
1 | 从AbstractClinicTests-context.xml 和HibernateClinicTests-context.xml 加载 application context。 |
在 large-scale application 中,Spring configuration 通常分为多个 files。因此,configuration 位置通常在 common base class 中为所有 application-specific integration 测试指定。这样的 base class 也可以添加有用的实例变量(自然地由依赖注入填充),例如在使用 Hibernate 的 application 的情况下SessionFactory
。
您应尽可能在 integration 测试中使用与部署环境中完全相同的 Spring configuration files。一个可能的差异点涉及数据库连接池和 transaction 基础结构。如果要部署到 full-blown application 服务器,则可能使用其连接池(可通过 JNDI 获得)和 JTA implementation。因此,在 production 中,您可以使用JndiObjectFactoryBean
或<jee:jndi-lookup>
作为DataSource
和JtaTransactionManager
。 JNDI 和 JTA 在 out-of-container integration 测试中不可用,因此您应该使用 Commons _DBCP BasicDataSource
和DataSourceTransactionManager
或HibernateTransactionManager
这样的组合。您可以将此变体行为分解为单个 XML 文件,可以选择 application 服务器和与所有其他 configuration 分离的“本地”configuration,这在 test 和 production 环境之间不会有所不同。此外,我们建议您使用 properties files 进行连接设置。有关 example,请参阅 PetClinic application。
更多资源
有关测试的更多信息,请参阅以下资源:
- JUnit:“A programmer-oriented 测试 framework for Java”。由 Spring Framework 在其测试套件中使用。
- TestNG:受 JUnit 启发的测试 framework,增加了对 annotations,测试组,data-driven 测试,分布式测试和其他 features 的支持。
- AssertJ:“Fluent 断言为 Java”,包括对 Java 8 lambdas,流和其他 features 的支持。
- Mock Objects:维基百科中的文章。
- MockObjects.com:Web 网站致力于 mock objects,这是一种在 test-driven 开发中改进 code 设计的技术。
- Mockito:Java mock library 基于Test Spy pattern。
- EasyMock:Java library“通过使用 Java 的代理机制动态生成它们,为接口提供 Mock Objects(以及通过 class 扩展提交 objects)。”由 Spring Framework 在其测试套件中使用。
- JMock:Library 支持使用 mock objects 开发 Java code test-driven。
- DbUnit:针对 database-driven 项目的 JUnit 扩展(也可用于 Ant 和 Maven),除其他外,将数据库置于测试运行之间的已知 state 中。
- 磨床:Java 负载测试 framework。