测试

1. Spring 测试简介

测试是企业软件开发不可或缺的一部分。本章重点介绍 IoC 原则对单元测试的附加值和好处 Spring 框架对集成测试的支持。(一个 对企业中测试的全面处理超出了本参考的范围 手册。spring-doc.cadn.net.cn

2. 单元测试

依赖项注入应该使您的代码对容器的依赖程度低于它 与传统的 Java EE 开发。组成应用程序的 POJO 应该 可在 JUnit 或 TestNG 测试中进行测试,并使用new运算符,没有 Spring 或任何其他容器。您可以使用 mock 对象 (与其他有价值的测试技术结合使用) 来单独测试您的代码。 如果遵循 Spring 的体系结构建议,则生成的 Clean Layering 代码库的组件化有助于更轻松地进行单元测试。例如 您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象, 无需在运行单元测试时访问持久性数据。spring-doc.cadn.net.cn

真正的单元测试通常运行得非常快,因为没有运行时基础设施 建立。强调真正的单元测试作为开发方法的一部分可以促进 您的工作效率。你可能不需要测试章节的这一部分来帮助你编写 对基于 IoC 的应用程序进行有效的单元测试。对于某些单元测试场景, 但是,Spring 框架提供了 mock 对象和测试支持类,这些 在本章中进行了介绍。spring-doc.cadn.net.cn

2.1. 模拟对象

Spring 包含许多专门用于 mock 的包:spring-doc.cadn.net.cn

2.1.1. 环境

org.springframework.mock.envpackage 包含EnvironmentPropertySourceabstractions 中(参见 Bean 定义配置文件PropertySource抽象化).MockEnvironmentMockPropertySource对于开发很有用 容器外测试依赖于特定于环境的属性的代码。spring-doc.cadn.net.cn

2.1.2. JNDI

org.springframework.mock.jndipackage 包含 JNDI 的部分实现 SPI,可用于为测试套件或独立设置简单的 JNDI 环境 应用。例如,如果 JDBCDataSource实例绑定到同一个 JNDI 名称,因此您可以重用这两个应用程序代码 以及测试场景中的配置。spring-doc.cadn.net.cn

模拟 JNDI 支持org.springframework.mock.jndipackage 是 从 Spring Framework 5.2 开始正式弃用,取而代之的是 Third 的完整解决方案 Simple-JNDI 等各方。

2.1.3. Servlet API

org.springframework.mock.webpackage 包含一组全面的 Servlet API 模拟对象,这些对象对测试 Web 上下文、控制器和过滤器很有用。这些 mock 对象的目标是与 Spring 的 Web MVC 框架一起使用,并且通常更多 比动态 mock 对象(如 EasyMock)更方便使用 或替代 Servlet API 模拟对象(例如 MockObjects)。spring-doc.cadn.net.cn

从 Spring Framework 5.0 开始,在org.springframework.mock.web是 基于 Servlet 4.0 API。

Spring MVC 测试框架构建在模拟 Servlet API 对象之上,以提供 Spring MVC 的集成测试框架。请参阅 MockMvcspring-doc.cadn.net.cn

2.1.4. Spring Web 响应式

org.springframework.mock.http.server.reactivepackage 包含 mock 实现 之ServerHttpRequestServerHttpResponse用于 WebFlux 应用程序。这org.springframework.mock.web.serverpackage 包含一个 mockServerWebExchange那 依赖于这些 mock 请求和响应对象。spring-doc.cadn.net.cn

MockServerHttpRequestMockServerHttpResponse从同一摘要中扩展 基类作为特定于服务器的实现,并与它们共享行为。为 例如,模拟请求在创建后是不可变的,但您可以使用mutate()方法 从ServerHttpRequest以创建修改后的实例。spring-doc.cadn.net.cn

为了使 mock 响应正确实现写入协定并返回 写入完成句柄(即Mono<Void>),则默认情况下,它使用Fluxcache().then(),它缓冲数据并使其可用于测试中的断言。 应用程序可以设置自定义写入函数(例如,测试无限流)。spring-doc.cadn.net.cn

WebTestClient 基于模拟请求和响应构建,以提供对 在没有 HTTP 服务器的情况下测试 WebFlux 应用程序。客户端还可用于 使用正在运行的服务器进行端到端测试。spring-doc.cadn.net.cn

2.2. 单元测试支持类

Spring 包含许多可以帮助进行单元测试的类。他们分为两个 类别:spring-doc.cadn.net.cn

2.2.1. 通用测试工具

org.springframework.test.utilpackage 包含几个通用实用程序 用于单元和集成测试。spring-doc.cadn.net.cn

AopTestUtils是 与 AOP 相关的实用程序方法。您可以使用这些方法获取对 隐藏在一个或多个 Spring 代理后面的底层目标对象。例如,如果你 通过使用 EasyMock 或 Mockito 等库将 bean 配置为动态模拟, 并且 mock 包装在 Spring 代理中,则可能需要直接访问底层 mock 来配置对它的期望并执行验证。对于 Spring 的核心 AOP 实用程序,请参阅AopUtilsAopProxyUtils.spring-doc.cadn.net.cn

ReflectionTestUtils是一个 基于反射的实用程序方法的集合。您可以在测试中使用这些方法 需要更改常量值的情况下,请将非public田 调用非publicsetter 方法,或调用非 -public配置或生命周期 callback 方法,例如:spring-doc.cadn.net.cn

  • 纵容privateprotected田 访问,而不是public域实体中属性的 setter 方法。spring-doc.cadn.net.cn

  • Spring 对注解(例如@Autowired,@Inject@Resource), 为privateprotected字段、setter 方法、 和配置方法。spring-doc.cadn.net.cn

  • 使用注释,例如@PostConstruct@PreDestroy对于生命周期回调 方法。spring-doc.cadn.net.cn

TestSocketUtils是一个简单的 用于查找可用 TCP 端口的实用程序localhost用于集成测试 场景。spring-doc.cadn.net.cn

TestSocketUtils可用于在 可用的随机端口。但是,这些实用程序不保证后续的 可用性,因此不可靠。而不是使用TestSocketUtils要查找服务器的可用本地端口,建议 您依赖于服务器在它选择或所在的随机临时端口上启动的能力 由作系统分配。要与该服务器交互,您应该查询 server 的端口。spring-doc.cadn.net.cn

2.2.2. Spring MVC 测试工具

org.springframework.test.web软件包包含ModelAndViewAssert,而您 可以与 JUnit、TestNG 或任何其他测试框架结合使用进行单元测试 处理 Spring MVCModelAndView对象。spring-doc.cadn.net.cn

对 Spring MVC 控制器进行单元测试
对 Spring MVC 进行单元测试Controller类作为 POJO 时,请使用ModelAndViewAssert结合MockHttpServletRequest,MockHttpSession等等。为了对您的 Spring MVC 和 RESTController类与您的WebApplicationContext配置,请改用 Spring MVC 测试框架

3. 集成测试

本节(本章其余大部分内容)涵盖了 Spring 的集成测试 应用。它包括以下主题:spring-doc.cadn.net.cn

3.1. 概述

能够在不需要的情况下执行一些集成测试非常重要 部署到您的应用程序服务器或连接到其他企业基础设施。 这样做可以让你测试以下内容:spring-doc.cadn.net.cn

Spring Framework 为spring-test模块。实际 JAR 文件的名称可能包括发行版本 并且也可能在长期org.springframework.test表单,具体取决于您获取的位置 it from (有关说明,请参阅 Dependency Management 部分)。此库包括org.springframework.test包,其中 包含用于与 Spring 容器进行集成测试的有价值的类。此测试 不依赖于应用程序服务器或其他部署环境。此类测试是 运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者 依赖于部署到应用程序服务器的远程测试。spring-doc.cadn.net.cn

单元和集成测试支持以 Comments 驱动的 Spring TestContext Framework 的形式提供。TestContext 框架是 与使用的实际测试框架无关,允许对测试进行检测 在各种环境中,包括 JUnit、TestNG 等。spring-doc.cadn.net.cn

3.2. 集成测试的目标

Spring 的集成测试支持具有以下主要目标:spring-doc.cadn.net.cn

接下来的几节描述了每个目标,并提供了指向实现和 配置详细信息。spring-doc.cadn.net.cn

3.2.1. 上下文管理和缓存

Spring TestContext 框架提供了 Spring 的一致加载ApplicationContextinstances 和WebApplicationContext实例以及缓存 的那些背景。支持缓存已加载的上下文非常重要,因为 启动时间可能会成为一个问题 — 不是因为 Spring 本身的开销,而是 因为 Spring 容器实例化的对象需要时间来实例化。为 例如,具有 50 到 100 个 Hibernate 映射文件的项目可能需要 10 到 20 秒才能完成 加载映射文件,并在每个测试中运行每个测试之前产生该成本 夹具会导致整体测试运行速度变慢,从而降低开发人员的工作效率。spring-doc.cadn.net.cn

测试类通常声明 XML 或 Groovy 的资源位置数组 配置元数据(通常在 Classpath 中)或组件类数组 用于配置应用程序。这些位置或类与 或 与web.xml或其他配置文件进行生产 部署。spring-doc.cadn.net.cn

默认情况下,加载后,配置的ApplicationContext将重复用于每个测试。 因此,每个测试套件和后续测试执行仅产生一次设置成本 要快得多。在这种情况下,术语“测试套件”是指所有测试都在同一环境中运行 JVM — 例如,所有测试都从给定项目的 Ant、Maven 或 Gradle 构建运行 或模块。在不太可能的情况下,测试会破坏应用程序上下文,并且需要 重新加载(例如,通过修改 Bean 定义或应用程序的状态 对象),TestContext 框架可以配置为重新加载配置,并且 在执行下一个测试之前重新构建应用程序上下文。spring-doc.cadn.net.cn

3.2.2. 测试 Fixture 的依赖注入

当 TestContext 框架加载您的应用程序上下文时,它可以选择: 使用 Dependency Injection 配置测试类的实例。这提供了一个 通过使用 应用程序上下文。这里的一个很大好处是您可以重用应用程序上下文 在各种测试场景中(例如,用于配置 Spring 托管对象 图形、交易代理、DataSource实例等),从而避免了 需要为单个测试用例复制复杂的测试夹具设置。spring-doc.cadn.net.cn

例如,考虑一个场景,我们有一个类 (HibernateTitleRepository) 实现Titledomain 实体。我们想写 测试以下领域的集成测试:spring-doc.cadn.net.cn

  • Spring 配置:基本上,就是与HibernateTitleRepositorybean 正确和现在?spring-doc.cadn.net.cn

  • Hibernate 映射文件配置:所有内容是否都正确映射,并且 正确的延迟加载设置是否正确?spring-doc.cadn.net.cn

  • 的逻辑HibernateTitleRepository:该类的已配置实例 按预期执行?spring-doc.cadn.net.cn

请参阅使用 TestContext 框架对测试 fixture 进行依赖注入。spring-doc.cadn.net.cn

3.2.3. 事务管理

在访问真实数据库的测试中,一个常见的问题是它们对 持久化存储。即使您使用开发数据库,对状态的更改也可能 影响未来的测试。此外,还有许多作 — 例如插入或修改持久性 data — 不能在事务之外执行(或验证)。spring-doc.cadn.net.cn

TestContext 框架解决了这个问题。默认情况下,框架会创建 和 为每个测试回滚一个事务。您可以编写可以假定存在的代码 交易的如果您在测试中调用事务代理对象,则它们的行为 正确地,根据他们配置的事务语义。此外,如果测试 method 在事务中运行时删除所选表的内容 managed,则事务默认回滚,并且数据库返回到 它在执行测试之前的状态。事务性支持由 使用PlatformTransactionManagerbean 的 bean 定义。spring-doc.cadn.net.cn

如果您希望事务提交(不常见,但当您希望 particular test 来填充或修改数据库),你可以告诉 TestContext 框架来使事务提交而不是回滚,方法是使用@Commit注解。spring-doc.cadn.net.cn

请参阅使用 TestContext 框架进行事务管理。spring-doc.cadn.net.cn

3.2.4. 集成测试的支持类

Spring TestContext 框架提供了几个abstract支持类 简化集成测试的编写。这些基本测试类提供了定义明确的 钩子以及方便的实例变量和方法, 这样,您就可以访问:spring-doc.cadn.net.cn

  • ApplicationContext,用于执行显式 bean 查找或测试 整个环境。spring-doc.cadn.net.cn

  • 一个JdbcTemplate,用于执行 SQL 语句来查询数据库。你可以使用这样的 查询以确认数据库相关 应用程序代码,并且 Spring 确保此类查询在相同的 transaction 作为应用程序代码。与 ORM 工具结合使用时,请确保 以避免误报spring-doc.cadn.net.cn

此外,您可能希望使用 特定于您的项目的实例变量和方法。spring-doc.cadn.net.cn

请参阅 TestContext 框架的支持类。spring-doc.cadn.net.cn

3.3. JDBC 测试支持

org.springframework.test.jdbc软件包包含JdbcTestUtils,它是一个 旨在简化标准数据库的 JDBC 相关实用程序函数的集合 测试场景。具体说来JdbcTestUtils提供以下静态实用程序 方法。spring-doc.cadn.net.cn

spring-jdbc模块为配置和启动嵌入式 database,您可以在与数据库交互的集成测试中使用。 有关详细信息,请参阅嵌入式数据库 支持和测试数据访问 具有嵌入式数据库的 Logicspring-doc.cadn.net.cn

3.4. 注解

本节介绍在测试 Spring 应用程序时可以使用的注释。 它包括以下主题:spring-doc.cadn.net.cn

3.4.1. Spring 测试注解

Spring Framework 提供了以下一组特定于 Spring 的 Comments,您可以 可以在单元测试和集成测试中与 TestContext 框架结合使用。 有关更多信息,请参阅相应的 javadoc,包括 default 属性 值、属性别名和其他详细信息。spring-doc.cadn.net.cn

Spring 的 testing 注释包括以下内容:spring-doc.cadn.net.cn

@BootstrapWith

@BootstrapWith是一个类级注解,可用于配置 Spring TestContext Framework 是引导的。具体来说,您可以使用@BootstrapWith自 指定自定义TestContextBootstrapper.有关更多详细信息,请参阅有关引导 TestContext 框架的部分。spring-doc.cadn.net.cn

@ContextConfiguration

@ContextConfiguration定义用于确定如何 加载并配置ApplicationContext用于集成测试。具体说来@ContextConfiguration声明应用程序上下文资源locations或 元件classes用于加载上下文。spring-doc.cadn.net.cn

资源位置通常是 XML 配置文件或位于 classpath 的@Configuration类。然而 资源位置还可以引用文件系统和组件中的文件和脚本 类可以是@Component@Service类,依此类推。有关更多详细信息,请参阅 Component Classesspring-doc.cadn.net.cn

以下示例显示了@ContextConfiguration引用 XML 的注释 文件:spring-doc.cadn.net.cn

Java
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
1 引用 XML 文件。
Kotlin
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
1 引用 XML 文件。

以下示例显示了@ContextConfiguration注解:spring-doc.cadn.net.cn

Java
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
1 引用类。
Kotlin
@ContextConfiguration(classes = [TestConfig::class]) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
1 引用类。

作为替代方案或除了声明资源位置或组件类之外, 您可以使用@ContextConfiguration声明ApplicationContextInitializer类。 以下示例显示了这种情况:spring-doc.cadn.net.cn

Java
@ContextConfiguration(initializers = CustomContextInitializer.class) (1)
class ContextInitializerTests {
    // class body...
}
1 声明初始值设定项类。
Kotlin
@ContextConfiguration(initializers = [CustomContextInitializer::class]) (1)
class ContextInitializerTests {
    // class body...
}
1 声明初始值设定项类。

您可以选择使用@ContextConfiguration要声明ContextLoader策略设置为 井。但请注意,您通常不需要显式配置 loader, 由于默认 loader 支持initializers和任一资源locations或 元件classes.spring-doc.cadn.net.cn

以下示例同时使用 location 和 loader:spring-doc.cadn.net.cn

Java
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
1 配置位置和自定义加载程序。
Kotlin
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
1 配置位置和自定义加载程序。
@ContextConfiguration提供对继承资源位置的支持,或者 配置类以及由超类声明的上下文初始化器 或封闭类。

请参阅 Context Management@Nestedtest 类配置@ContextConfigurationjavadocs 了解更多详细信息。spring-doc.cadn.net.cn

@WebAppConfiguration

@WebAppConfiguration是一个类级注解,可用于声明ApplicationContextloaded for an integration test 应为WebApplicationContext. 仅仅存在@WebAppConfiguration在测试类上确保WebApplicationContext为测试加载,使用默认值"file:src/main/webapp"对于 Web 应用程序根目录的路径(即 resource base path) 的 Sample。资源基路径在后台用于创建MockServletContext,它用作ServletContext对于测试的WebApplicationContext.spring-doc.cadn.net.cn

以下示例演示如何使用@WebAppConfiguration注解:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
Kotlin
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
1 @WebAppConfiguration注解。

要覆盖默认值,您可以使用 含蓄value属性。双classpath:file:资源前缀为 支持。如果未提供资源前缀,则假定该路径为文件系统 资源。下面的示例展示了如何指定 Classpath 资源:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
1 指定 Classpath 资源。
Kotlin
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
1 指定 Classpath 资源。

请注意,@WebAppConfiguration必须与@ContextConfiguration,在单个测试类或测试类中 等级制度。请参阅@WebAppConfigurationjavadoc 了解更多详情。spring-doc.cadn.net.cn

@ContextHierarchy

@ContextHierarchy是类级注解,用于定义ApplicationContext实例进行集成测试。@ContextHierarchy应该是 使用一个或多个@ContextConfiguration实例,每个实例 定义上下文层次结构中的级别。以下示例演示了@ContextHierarchy在单个测试类 (@ContextHierarchy也可以使用 在 Test 类层次结构中):spring-doc.cadn.net.cn

Java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
    // class body...
}
Kotlin
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
    // class body...
}
Java
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
    // class body...
}
Kotlin
@WebAppConfiguration
@ContextHierarchy(
        ContextConfiguration(classes = [AppConfig::class]),
        ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
    // class body...
}

如果需要合并或覆盖给定上下文级别的配置 层次结构中,您必须通过提供 与name属性@ContextConfiguration在每个对应的 level 的 LEVEL 中。请参阅 Context Hierarchies@ContextHierarchyJavadoc 以获取更多示例。spring-doc.cadn.net.cn

@ActiveProfiles

@ActiveProfiles是用于声明哪个 bean 的类级 Comments 定义配置文件在加载ApplicationContext对于 集成测试。spring-doc.cadn.net.cn

以下示例指示devprofile 应处于活动状态:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
1 指示devprofile 应处于活动状态。
Kotlin
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
1 指示devprofile 应处于活动状态。

以下示例指示devintegration配置文件应 保持活跃:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
    // class body...
}
1 指示devintegration配置文件应处于活动状态。
Kotlin
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
    // class body...
}
1 指示devintegration配置文件应处于活动状态。
@ActiveProfiles支持继承活动的 Bean 定义配置文件 由 superclasses 声明,并默认封闭类。您还可以解析 active bean 定义通过实现自定义ActiveProfilesResolver并使用resolver属性@ActiveProfiles.
@TestPropertySource

@TestPropertySource是类级注解,可用于配置 要添加到PropertySourcesEnvironment对于ApplicationContextloaded 为 集成测试。spring-doc.cadn.net.cn

下面的示例演示了如何从 Classpath 声明属性文件:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 从以下位置获取属性test.properties在 Classpath 的根目录中。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 从以下位置获取属性test.properties在 Classpath 的根目录中。

下面的示例演示如何声明内联属性:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
    // class body...
}
1 timezoneport性能。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
1 timezoneport性能。
@DynamicPropertySource

@DynamicPropertySource是一个方法级注释,可用于注册要添加到PropertySourcesEnvironment为 一ApplicationContextloaded 进行集成测试。动态属性很有用 当您不知道 properties 的值时 — 例如,如果 properties 由外部资源管理,例如由 Testcontainers 项目管理的容器。spring-doc.cadn.net.cn

下面的示例演示如何注册动态属性:spring-doc.cadn.net.cn

Java
@ContextConfiguration
class MyIntegrationTests {

    static MyExternalServer server = // ...

    @DynamicPropertySource (1)
    static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
        registry.add("server.port", server::getPort); (3)
    }

    // tests ...
}
1 注释staticmethod 替换为@DynamicPropertySource.
2 接受DynamicPropertyRegistry作为参数。
3 注册动态server.port属性从服务器延迟检索。
Kotlin
@ContextConfiguration
class MyIntegrationTests {

    companion object {

        @JvmStatic
        val server: MyExternalServer = // ...

        @DynamicPropertySource (1)
        @JvmStatic
        fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
            registry.add("server.port", server::getPort) (3)
        }
    }

    // tests ...
}
1 注释staticmethod 替换为@DynamicPropertySource.
2 接受DynamicPropertyRegistry作为参数。
3 注册动态server.port属性从服务器延迟检索。
@DirtiesContext

@DirtiesContext表示底层的 SpringApplicationContext已经 在执行测试期间被弄脏(即,测试在 某种方式 — 例如,通过更改单例 bean 的状态),并且应该是 闭。当应用程序上下文被标记为 dirty 时,它会从测试中删除 framework 的 cache 和 closed。因此,底层的 Spring 容器是 为需要具有相同配置的上下文的任何后续测试重新构建 元数据。spring-doc.cadn.net.cn

您可以使用@DirtiesContext作为类级和方法级注解 相同的类或类层次结构。在这种情况下,ApplicationContext已标记 作为 dirty 在任何此类 Comments 方法之前或之后,以及当前 test 类,具体取决于配置的methodModeclassMode.spring-doc.cadn.net.cn

以下示例说明了何时会为各种 配置场景:spring-doc.cadn.net.cn

  • 在当前测试类之前,当在类模式设置为BEFORE_CLASS.spring-doc.cadn.net.cn

    Java
    @DirtiesContext(classMode = BEFORE_CLASS) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在当前测试类之前弄脏上下文。
    Kotlin
    @DirtiesContext(classMode = BEFORE_CLASS) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在当前测试类之前弄脏上下文。
  • 在当前测试类之后,当在类模式设置为AFTER_CLASS(即默认的类模式)。spring-doc.cadn.net.cn

    Java
    @DirtiesContext (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 弄脏当前测试类之后的上下文。
    Kotlin
    @DirtiesContext (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 弄脏当前测试类之后的上下文。
  • 在当前测试类中的每个测试方法之前,当在具有 class mode 设置为BEFORE_EACH_TEST_METHOD.spring-doc.cadn.net.cn

    Java
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在每个测试方法之前弄脏上下文。
    Kotlin
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在每个测试方法之前弄脏上下文。
  • 在当前测试类中的每个测试方法之后,当在类 mode 设置为AFTER_EACH_TEST_METHOD.spring-doc.cadn.net.cn

    Java
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 在每个测试方法之后弄脏上下文。
    Kotlin
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 在每个测试方法之后弄脏上下文。
  • 在当前测试之前,当在方法模式设置为BEFORE_METHOD.spring-doc.cadn.net.cn

    Java
    @DirtiesContext(methodMode = BEFORE_METHOD) (1)
    @Test
    void testProcessWhichRequiresFreshAppCtx() {
        // some logic that requires a new Spring container
    }
    1 在当前测试方法之前弄脏上下文。
    Kotlin
    @DirtiesContext(methodMode = BEFORE_METHOD) (1)
    @Test
    fun testProcessWhichRequiresFreshAppCtx() {
        // some logic that requires a new Spring container
    }
    1 在当前测试方法之前弄脏上下文。
  • 在当前测试之后,当在方法模式设置为AFTER_METHOD(即默认方法模式)。spring-doc.cadn.net.cn

    Java
    @DirtiesContext (1)
    @Test
    void testProcessWhichDirtiesAppCtx() {
        // some logic that results in the Spring container being dirtied
    }
    1 在当前测试方法之后弄脏上下文。
    Kotlin
    @DirtiesContext (1)
    @Test
    fun testProcessWhichDirtiesAppCtx() {
        // some logic that results in the Spring container being dirtied
    }
    1 在当前测试方法之后弄脏上下文。

如果您使用@DirtiesContext在其 context 配置为 context 一部分的测试中 hierarchy 替换为@ContextHierarchy中,您可以使用hierarchyModeflag 来控制方式 上下文缓存将被清除。默认情况下,使用穷举算法来清除 context cache,不仅包括当前级别,还包括所有其他上下文 共享当前测试通用的上级上下文的层次结构。都ApplicationContext驻留在公共祖先的子层次结构中的实例 context 将从上下文缓存中删除并关闭。如果穷举算法是 overkill 对于特定用例,您可以指定更简单的 current level 算法 如下例所示。spring-doc.cadn.net.cn

Java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class BaseTests {
    // class body...
}

class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    void test() {
        // some logic that results in the child context being dirtied
    }
}
1 使用 current-level 算法。
Kotlin
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
open class BaseTests {
    // class body...
}

class ExtendedTests : BaseTests() {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    fun test() {
        // some logic that results in the child context being dirtied
    }
}
1 使用 current-level 算法。

有关EXHAUSTIVECURRENT_LEVELalgorithms,请参阅DirtiesContext.HierarchyModejavadoc 的spring-doc.cadn.net.cn

@TestExecutionListeners

@TestExecutionListeners用于为特定测试类注册侦听器,则其 子类及其嵌套类。如果您希望全局注册侦听器,请 应通过TestExecutionListener配置.spring-doc.cadn.net.cn

以下示例显示了如何注册两个TestExecutionListener实现:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
1 注册 2TestExecutionListener实现。
Kotlin
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
1 注册 2TestExecutionListener实现。

默认情况下,@TestExecutionListeners支持从 超类或封闭类。看@Nestedtest 类配置@TestExecutionListenersJavadoc有关示例和更多详细信息。如果您发现需要切换 回到使用默认的TestExecutionListenerimplementations,请参阅注释 在注册TestExecutionListener实现.spring-doc.cadn.net.cn

@RecordApplicationEvents

@RecordApplicationEvents是一个类级注释,用于指示 Spring TestContext 框架记录在ApplicationContext在执行单个测试期间。spring-doc.cadn.net.cn

记录的事件可以通过ApplicationEvents测试中的 API。spring-doc.cadn.net.cn

请参阅 应用程序事件@RecordApplicationEventsJavadoc有关示例和更多详细信息。spring-doc.cadn.net.cn

@Commit

@Commit指示事务测试方法的事务应为 在测试方法完成后提交。您可以使用@Commit作为直接 替代@Rollback(false)以更明确地传达代码的意图。 类似于@Rollback,@Commit也可以声明为类级或方法级 注解。spring-doc.cadn.net.cn

以下示例演示如何使用@Commit注解:spring-doc.cadn.net.cn

Java
@Commit (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
1 将测试结果提交到数据库。
Kotlin
@Commit (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
1 将测试结果提交到数据库。
@Rollback

@Rollback指示事务测试方法的事务是否应为 在测试方法完成后回滚。如果true,则事务将滚动 返回。否则,将提交事务(另请参阅@Commit).Spring 中集成测试的回滚 TestContext Framework 默认为true便@Rollback未显式声明。spring-doc.cadn.net.cn

当声明为类级注释时,@Rollback定义默认回滚 Test Class 层次结构中所有测试方法的语义。当声明为 方法级注解,@Rollback定义特定测试的回滚语义 方法,可能会覆盖类级@Rollback@Commit语义学。spring-doc.cadn.net.cn

以下示例导致测试方法的结果不回滚(即 result 提交到数据库):spring-doc.cadn.net.cn

Java
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
1 不要回滚结果。
Kotlin
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
1 不要回滚结果。
@BeforeTransaction

@BeforeTransaction表示带注释的void方法应在 事务,对于已配置为在 transaction 使用 Spring 的@Transactional注解。@BeforeTransaction方法 不需要public并且可以在基于 Java 8 的接口 default 上声明 方法。spring-doc.cadn.net.cn

以下示例演示如何使用@BeforeTransaction注解:spring-doc.cadn.net.cn

Java
@BeforeTransaction (1)
void beforeTransaction() {
    // logic to be run before a transaction is started
}
1 在事务之前运行该方法。
Kotlin
@BeforeTransaction (1)
fun beforeTransaction() {
    // logic to be run before a transaction is started
}
1 在事务之前运行该方法。
@AfterTransaction

@AfterTransaction表示带注释的void方法应在 transaction 的 transaction 使用 Spring 的@Transactional注解。@AfterTransaction方法 不需要public并且可以在基于 Java 8 的接口 default 上声明 方法。spring-doc.cadn.net.cn

Java
@AfterTransaction (1)
void afterTransaction() {
    // logic to be run after a transaction has ended
}
1 在事务后运行该方法。
Kotlin
@AfterTransaction (1)
fun afterTransaction() {
    // logic to be run after a transaction has ended
}
1 在事务后运行该方法。
@Sql

@Sql用于注释测试类或测试方法,以配置要运行的 SQL 脚本 在集成测试期间针对给定数据库。以下示例演示如何使用 它:spring-doc.cadn.net.cn

Java
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
void userTest() {
    // run code that relies on the test schema and test data
}
1 为此测试运行两个脚本。
Kotlin
@Test
@Sql("/test-schema.sql", "/test-user-data.sql") (1)
fun userTest() {
    // run code that relies on the test schema and test data
}
1 为此测试运行两个脚本。
@SqlConfig

@SqlConfig定义用于确定如何解析和运行 SQL 脚本的元数据 配置了@Sql注解。以下示例演示如何使用它:spring-doc.cadn.net.cn

Java
@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
void userTest() {
    // run code that relies on the test data
}
1 在 SQL 脚本中设置注释前缀和分隔符。
Kotlin
@Test
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) (1)
fun userTest() {
    // run code that relies on the test data
}
1 在 SQL 脚本中设置注释前缀和分隔符。
@SqlMergeMode

@SqlMergeMode用于注释测试类或测试方法,以配置 方法级别@Sql声明与 Class-level 合并@Sql声明。如果@SqlMergeMode未在测试类或测试方法上声明,则OVERRIDE合并模式 将默认使用。使用OVERRIDE模式,方法级@Sql声明将 有效覆盖类级别@Sql声明。spring-doc.cadn.net.cn

请注意,方法级别的@SqlMergeModedeclaration 覆盖类级声明。spring-doc.cadn.net.cn

以下示例演示如何使用@SqlMergeMode在类级别。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 @Sqlmerge 模式设置为MERGE对于类中的所有测试方法。
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 @Sqlmerge 模式设置为MERGE对于类中的所有测试方法。

以下示例演示如何使用@SqlMergeMode在方法级别。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 @Sqlmerge 模式设置为MERGE对于特定的测试方法。
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 @Sqlmerge 模式设置为MERGE对于特定的测试方法。
@SqlGroup

@SqlGroup是一个容器注解,它聚合了多个@Sql附注。您可以 用@SqlGroup本机来声明几个嵌套的@Sqlannotations 的 Comments,或者你可以使用它 与 Java 8 对可重复注释的支持相结合,其中@Sql可以是 在同一个类或方法上多次声明,隐式生成此容器 注解。以下示例说明如何声明 SQL 组:spring-doc.cadn.net.cn

Java
@Test
@SqlGroup({ (1)
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
1 声明一组 SQL 脚本。
Kotlin
@Test
@SqlGroup( (1)
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // run code that uses the test schema and test data
}
1 声明一组 SQL 脚本。

3.4.2. 标准注解支持

以下注释支持所有配置的标准语义 Spring TestContext 框架。请注意,这些注释并非特定于测试 并且可以在 Spring Framework 中的任何位置使用。spring-doc.cadn.net.cn

JSR-250 生命周期注释

在 Spring TestContext 框架中,你可以使用@PostConstruct@PreDestroy跟 在ApplicationContext. 但是,这些生命周期注释在实际测试类中的使用受到限制。spring-doc.cadn.net.cn

如果测试类中的方法带有@PostConstruct,该方法运行 在底层测试框架的任何 before 方法(例如,方法 用 JUnit Jupiter 的@BeforeEach),这适用于 test 类。另一方面,如果测试类中的方法使用@PreDestroy,该方法永远不会运行。因此,在 test 类中,我们建议 您可以使用来自底层测试框架的测试生命周期回调,而不是@PostConstruct@PreDestroy.spring-doc.cadn.net.cn

3.4.3. Spring JUnit 4 测试注解

@IfProfileValue

@IfProfileValue表示为特定测试启用了带注释的测试 环境。如果配置的ProfileValueSource返回匹配的value对于 提供name,则测试已启用。否则,测试将被禁用,并且实际上 忽视。spring-doc.cadn.net.cn

您可以申请@IfProfileValue在类级别和/或方法级别。 类级别用法@IfProfileValue优先于任何 方法。具体来说,如果测试是 在类级别和方法级别都启用。缺少@IfProfileValue表示测试已隐式启用。这类似于 JUnit 4 的@Ignore注解,但存在@Ignore始终禁用测试。spring-doc.cadn.net.cn

以下示例显示了一个测试,该测试具有@IfProfileValue注解:spring-doc.cadn.net.cn

Java
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
1 仅当 Java 供应商为“Oracle Corporation”时,才运行此测试。
Kotlin
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
fun testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
1 仅当 Java 供应商为“Oracle Corporation”时,才运行此测试。

或者,您可以配置@IfProfileValue其中,列表为values(使用ORsemantics) 实现对 JUnit 4 环境中测试组的类似 TestNG 的支持。 请考虑以下示例:spring-doc.cadn.net.cn

Java
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
1 对单元测试和集成测试运行此测试。
Kotlin
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) (1)
@Test
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
1 对单元测试和集成测试运行此测试。
@ProfileValueSourceConfiguration

@ProfileValueSourceConfiguration是指定类型的类级注释 之ProfileValueSource在检索通过@IfProfileValue注解。如果@ProfileValueSourceConfiguration未声明为 测试SystemProfileValueSource默认使用。以下示例说明如何 用@ProfileValueSourceConfiguration:spring-doc.cadn.net.cn

Java
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
    // class body...
}
1 使用自定义配置文件值源。
Kotlin
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) (1)
class CustomProfileValueSourceTests {
    // class body...
}
1 使用自定义配置文件值源。
@Timed

@Timed指示带注释的测试方法必须在指定的 时间段 (以毫秒为单位)。如果文本执行时间超过指定时间 期间,则测试失败。spring-doc.cadn.net.cn

该时间段包括运行测试方法本身、测试的任何重复(请参阅@Repeat),以及测试夹具的任何设置或拆除。以下内容 示例展示了如何使用它:spring-doc.cadn.net.cn

Java
@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
1 将测试的时间段设置为 1 秒。
Kotlin
@Timed(millis = 1000) (1)
fun testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
1 将测试的时间段设置为 1 秒。

Spring的@Timed注解的语义与 JUnit 4 的@Test(timeout=…​)支持。具体来说,由于 JUnit 4 处理测试执行超时的方式 (即,通过在单独的Thread),@Test(timeout=…​)如果测试时间过长,则抢先使测试失败。Spring的@Timed,另一方面 hand 不会抢先使测试失败,而是等待测试完成 在失败之前。spring-doc.cadn.net.cn

@Repeat

@Repeat表示必须重复运行带注释的测试方法。的 在注释中指定要运行测试方法的时间。spring-doc.cadn.net.cn

要重复的执行范围包括测试方法本身的执行,如 以及测试夹具的任何设置或拆除。当与SpringMethodRule,范围还包括 准备测试实例TestExecutionListener实现。这 以下示例显示了如何使用@Repeat注解:spring-doc.cadn.net.cn

Java
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
    // ...
}
1 重复此测试十次。
Kotlin
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
    // ...
}
1 重复此测试十次。

3.4.4. Spring JUnit Jupiter 测试注解

当与SpringExtension和 JUnit Jupiter (即 JUnit 5 中的编程模型):spring-doc.cadn.net.cn

@SpringJUnitConfig

@SpringJUnitConfig是一个组合@ExtendWith(SpringExtension.class)来自 JUnit Jupiter 和@ContextConfiguration从 Spring TestContext 框架。它可以在类级别用作 drop-in 替代@ContextConfiguration.对于配置选项,唯一的 区别@ContextConfiguration@SpringJUnitConfig是那个组件 类可以使用value属性@SpringJUnitConfig.spring-doc.cadn.net.cn

以下示例演示如何使用@SpringJUnitConfig注解来指定 configuration 类:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
1 指定配置类。
Kotlin
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
1 指定配置类。

以下示例演示如何使用@SpringJUnitConfig注解来指定 配置文件的位置:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
1 指定配置文件的位置。
Kotlin
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
1 指定配置文件的位置。

请参阅 Context Management 以及 javadoc 以获取@SpringJUnitConfig@ContextConfiguration了解更多详情。spring-doc.cadn.net.cn

@SpringJUnitWebConfig

@SpringJUnitWebConfig是一个组合@ExtendWith(SpringExtension.class)来自 JUnit Jupiter 和@ContextConfiguration@WebAppConfiguration来自 Spring TestContext Framework。您可以在课堂上使用它 level 作为@ContextConfiguration@WebAppConfiguration. 关于配置选项,唯一的区别是@ContextConfiguration@SpringJUnitWebConfig是,您可以使用value属性@SpringJUnitWebConfig.此外,您还可以覆盖value属性从@WebAppConfiguration只有使用resourcePath属性@SpringJUnitWebConfig.spring-doc.cadn.net.cn

以下示例演示如何使用@SpringJUnitWebConfig注解来指定 一个配置类:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
1 指定配置类。
Kotlin
@SpringJUnitWebConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
1 指定配置类。

以下示例演示如何使用@SpringJUnitWebConfig注解来指定 配置文件的位置:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
1 指定配置文件的位置。
Kotlin
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
1 指定配置文件的位置。
@TestConstructor

@TestConstructor是一个类型级注解,用于配置参数 的ApplicationContext.spring-doc.cadn.net.cn

如果@TestConstructor不存在或元存在于测试类中,则默认测试 构造函数 autowire 模式。有关如何更改的详细信息,请参阅下面的提示 默认模式。但是请注意,本地声明@Autowired在 constructor 优先于两者@TestConstructor和默认模式。spring-doc.cadn.net.cn

更改默认测试构造函数 autowire 模式

默认的测试构造函数 autowire 模式可以通过设置spring.test.constructor.autowire.modeJVM 系统属性设置为all.或者, 默认模式可以通过SpringProperties机制。spring-doc.cadn.net.cn

从 Spring Framework 5.3 开始,默认模式也可以配置为 JUnit Platform 配置参数spring-doc.cadn.net.cn

如果spring.test.constructor.autowire.mode属性未设置,则 test 类 构造函数不会自动装配。spring-doc.cadn.net.cn

从 Spring Framework 5.2 开始,@TestConstructor仅支持结合使用 使用SpringExtension用于 JUnit Jupiter。请注意,SpringExtension是 通常会自动为您注册 - 例如,当使用@SpringJUnitConfig@SpringJUnitWebConfig或各种与测试相关的注解 Spring Boot 测试。
@NestedTestConfiguration

@NestedTestConfiguration是一种类型级注释,用于配置 Spring 测试配置注释在封闭的类层次结构中处理 用于内部测试类。spring-doc.cadn.net.cn

如果@NestedTestConfiguration在测试类中不存在或元存在,则在其 supertype 层次结构,或者在其封闭类层次结构中,默认的封闭 将使用 Configuration Inheritance 模式。有关如何作的详细信息,请参阅下面的提示 更改 Default Mode。spring-doc.cadn.net.cn

更改默认的封闭配置继承模式

默认的封闭配置继承模式INHERIT,但可以是 通过设置spring.test.enclosing.configurationJVM 系统属性设置为OVERRIDE.或者,可以通过SpringProperties机制。spring-doc.cadn.net.cn

Spring TestContext 框架尊重@NestedTestConfigurationsemantics 的 以下注释。spring-doc.cadn.net.cn

的使用@NestedTestConfiguration通常只有在连用时才有意义 跟@NestedJUnit Jupiter 中的 test 类;但是,可能还有其他检查 支持 Spring 的框架和使用此 注解。

@Nestedtest 类配置有关示例和进一步 详。spring-doc.cadn.net.cn

@EnabledIf

@EnabledIf用于表示带注释的 JUnit Jupiter 测试类或测试方法 已启用,并且如果提供的expression计算结果为true. 具体来说,如果表达式的计算结果为Boolean.TRUEString等于true(忽略大小写),则启用测试。在类级别应用时,所有测试方法 默认情况下,该类也会自动启用。spring-doc.cadn.net.cn

表达式可以是以下任何一项:spring-doc.cadn.net.cn

但是请注意,如果文本文本不是 property placeholder 的实用价值为零,因为@EnabledIf("false")是 相当于@Disabled@EnabledIf("true")在逻辑上毫无意义。spring-doc.cadn.net.cn

您可以使用@EnabledIf作为元注释来创建自定义组合注释。为 示例,您可以创建自定义@EnabledOnMac注解如下:spring-doc.cadn.net.cn

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@EnabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Enabled on Mac OS"
)
annotation class EnabledOnMac {}

@EnabledOnMac只是作为可能性的示例。如果你有那个确切的 使用案例,请使用内置的@EnabledOnOs(MAC)JUnit Jupiter 中的支持。spring-doc.cadn.net.cn

从 JUnit 5.7 开始,JUnit Jupiter 还有一个名为@EnabledIf.因此 如果您希望使用 Spring 的@EnabledIf支持 确保导入 annotation 类型 从正确的包装中。spring-doc.cadn.net.cn

@DisabledIf

@DisabledIf用于指示带注释的 JUnit Jupiter 测试类或测试 方法已禁用,如果提供的expression计算结果为true.具体来说,如果表达式的计算结果为Boolean.TRUEString平等 自true(忽略大小写),则禁用测试。在类级别应用时,所有 该类中的 test 方法也会自动禁用。spring-doc.cadn.net.cn

表达式可以是以下任何一项:spring-doc.cadn.net.cn

但是请注意,如果文本文本不是 property placeholder 的实用价值为零,因为@DisabledIf("true")是 相当于@Disabled@DisabledIf("false")在逻辑上毫无意义。spring-doc.cadn.net.cn

您可以使用@DisabledIf作为元注释来创建自定义组合注释。为 示例,您可以创建自定义@DisabledOnMac注解如下:spring-doc.cadn.net.cn

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@DisabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Disabled on Mac OS"
)
annotation class DisabledOnMac {}

@DisabledOnMac只是作为可能性的示例。如果你有那个确切的 使用案例,请使用内置的@DisabledOnOs(MAC)JUnit Jupiter 中的支持。spring-doc.cadn.net.cn

从 JUnit 5.7 开始,JUnit Jupiter 还有一个名为@DisabledIf.因此 如果您希望使用 Spring 的@DisabledIf支持 确保导入 annotation 类型 从正确的包装中。spring-doc.cadn.net.cn

3.4.5. 测试的元注解支持

您可以使用大多数与测试相关的注释作为元注释来创建自定义组合 注释并减少测试套件中的配置重复。spring-doc.cadn.net.cn

您可以将以下各项作为 TestContext 框架的元注释。spring-doc.cadn.net.cn

请考虑以下示例:spring-doc.cadn.net.cn

Java
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
Kotlin
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }

如果我们发现我们在基于 JUnit 4 的 test 套件中,我们可以通过引入自定义组合注解来减少重复 集中了 Spring 的通用测试配置,如下所示:spring-doc.cadn.net.cn

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }

然后我们可以使用我们的自定义@TransactionalDevTestConfig注解来简化 配置基于 JUnit 4 的各个测试类,如下所示:spring-doc.cadn.net.cn

Java
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
Kotlin
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests

@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests

如果我们编写使用 JUnit Jupiter 的测试,我们可以进一步减少代码重复。 因为 JUnit 5 中的注解也可以用作元注解。请考虑以下 例:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
Kotlin
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }

如果我们发现我们在 JUnit 中重复上述配置 基于 Jupiter 的测试套件,我们可以通过引入自定义组合的 注解集中了 Spring 和 JUnit Jupiter 的通用测试配置, 如下:spring-doc.cadn.net.cn

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }

然后我们可以使用我们的自定义@TransactionalDevTestConfig注解来简化 配置基于 JUnit Jupiter 的各个测试类,如下所示:spring-doc.cadn.net.cn

Java
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }
Kotlin
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }

由于 JUnit Jupiter 支持使用@Test,@RepeatedTest,ParameterizedTest, 和其他作为元注释的注释,您还可以在 测试方法级别。例如,如果我们希望创建一个组合 这@Test@Tag来自 JUnit Jupiter 的注释,带有@Transactional注解,我们可以创建一个@TransactionalIntegrationTestannotation 中,作为 遵循:spring-doc.cadn.net.cn

Java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }

然后我们可以使用我们的自定义@TransactionalIntegrationTest注解来简化 配置基于 JUnit Jupiter 的各个测试方法,如下所示:spring-doc.cadn.net.cn

Java
@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }
Kotlin
@TransactionalIntegrationTest
fun saveOrder() { }

@TransactionalIntegrationTest
fun deleteOrder() { }

有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。spring-doc.cadn.net.cn

3.5. Spring TestContext 框架

Spring TestContext 框架(位于org.springframework.test.context包)提供通用的、注解驱动的单元和集成测试支持,即 与正在使用的测试框架无关。TestContext 框架还放置了一个很棒的 重视约定而不是配置,具有合理的默认值 可以通过基于注释的配置进行覆盖。spring-doc.cadn.net.cn

除了通用测试基础设施外,TestContext 框架还提供 显式支持 JUnit 4、JUnit Jupiter(又名 JUnit 5)和 TestNG。对于 JUnit 4 和 TestNG 中,Spring 提供了abstract支持类。此外,Spring 还提供了一个自定义的 JUnitRunner和自定义 JUnitRules对于 JUnit 4 和自定义Extension对于 JUnit Jupiter 允许您编写所谓的 POJO 测试类。POJO 测试类不是 需要扩展特定的类层次结构,例如abstract支持类。spring-doc.cadn.net.cn

以下部分概述了 TestContext 框架的内部结构。 如果您只对使用框架感兴趣,而对扩展它不感兴趣 使用您自己的自定义侦听器或自定义加载器,请随时直接转到 配置(上下文管理依赖关系注入事务 management)、support classesannotation support 部分。spring-doc.cadn.net.cn

3.5.1. 键抽象

框架的核心包括TestContextManager类和TestContext,TestExecutionListenerSmartContextLoader接口。一个TestContextManager为每个测试类创建(例如,为了执行 JUnit Jupiter 中单个测试类中的所有测试方法)。这TestContextManager, 反过来,管理TestContext,它保存当前测试的上下文。这TestContextManager还会更新TestContext随着测试的进行 和委托人TestExecutionListener实现,这些实现将实际的 通过提供依赖项注入、管理事务等来测试执行。一个SmartContextLoader负责加载ApplicationContext对于给定的测试 类。请参阅 javadoc 和 Spring test 套件,以获取更多信息和各种实现的示例。spring-doc.cadn.net.cn

TestContext

TestContext封装运行测试的上下文(与 实际测试框架),并为 它负责的 test 实例。这TestContext还会委托给SmartContextLoader要加载ApplicationContext如果需要。spring-doc.cadn.net.cn

TestContextManager

TestContextManager是 Spring TestContext 框架的主要入口点,并且是 负责管理单个TestContext并向每个已注册的TestExecutionListener在定义明确的测试执行点:spring-doc.cadn.net.cn

TestExecutionListener

TestExecutionListener定义用于响应 这TestContextManager侦听器注册到的 URL。看TestExecutionListener配置.spring-doc.cadn.net.cn

上下文加载器

ContextLoader是一个策略接口,用于加载ApplicationContext对于 由 Spring TestContext 框架管理的集成测试。您应该实施SmartContextLoader而不是这个接口来提供对组件类的支持, 活动 Bean 定义配置文件、测试属性源、上下文层次结构和WebApplicationContext支持。spring-doc.cadn.net.cn

SmartContextLoaderContextLoader接口,它取代了 原始最小ContextLoaderSPI 的 API 中。具体来说,SmartContextLoader可以选择 进程资源位置、组件类或上下文初始值设定项。此外,SmartContextLoader可以在 中设置活动的 Bean 定义配置文件并测试属性源 它加载的上下文。spring-doc.cadn.net.cn

Spring 提供了以下实现:spring-doc.cadn.net.cn

  • DelegatingSmartContextLoader:两个默认加载器之一,它在内部委托给 一AnnotationConfigContextLoader一个GenericXmlContextLoaderGenericGroovyXmlContextLoader,具体取决于为 test 类或是否存在 default locations 或 default configuration 类。 仅当 Groovy 位于 Classpath 上时,才会启用 Groovy 支持。spring-doc.cadn.net.cn

  • WebDelegatingSmartContextLoader:两个默认加载器之一,它在内部委托 更改为AnnotationConfigWebContextLoader一个GenericXmlWebContextLoaderGenericGroovyXmlWebContextLoader,具体取决于为 测试类或存在 default locations 或 default configuration 类。一个 webContextLoader仅在以下情况下使用@WebAppConfiguration位于 test 类。仅当 Groovy 位于 Classpath 上时,才会启用 Groovy 支持。spring-doc.cadn.net.cn

  • AnnotationConfigContextLoader:加载标准ApplicationContextfrom 组件 类。spring-doc.cadn.net.cn

  • AnnotationConfigWebContextLoader:加载一个WebApplicationContextfrom 组件 类。spring-doc.cadn.net.cn

  • GenericGroovyXmlContextLoader:加载标准ApplicationContext从资源 位置,可以是 Groovy 脚本或 XML 配置文件。spring-doc.cadn.net.cn

  • GenericGroovyXmlWebContextLoader:加载一个WebApplicationContext从资源 位置,可以是 Groovy 脚本或 XML 配置文件。spring-doc.cadn.net.cn

  • GenericXmlContextLoader:加载标准ApplicationContext从 XML 资源 地点。spring-doc.cadn.net.cn

  • GenericXmlWebContextLoader:加载一个WebApplicationContext从 XML 资源 地点。spring-doc.cadn.net.cn

3.5.2. 引导 TestContext 框架

Spring TestContext 框架内部的默认配置是 足以满足所有常见使用案例的需求。但是,有时开发团队或 第三方框架想要更改默认值ContextLoader,实现 习惯TestContextContextCache,则扩充默认的ContextCustomizerFactoryTestExecutionListenerimplementations 等。为 这种对 TestContext 框架运行方式的低级控制, Spring 提供了一个 引导策略。spring-doc.cadn.net.cn

TestContextBootstrapper定义用于引导 TestContext 框架的 SPI。一个TestContextBootstrapperTestContextManager加载TestExecutionListener实现,并构建TestContext它管理。您可以为 test 类(或 test class 层次结构)@BootstrapWith,直接或作为 meta-annotation 中。如果未使用@BootstrapWith,则DefaultTestContextBootstrapperWebTestContextBootstrapper,具体取决于是否存在@WebAppConfiguration.spring-doc.cadn.net.cn

由于TestContextBootstrapperSPI 将来可能会发生变化(以适应 new requirements),我们强烈建议实现者不要实现此接口 直接,而是扩展AbstractTestContextBootstrapper或它的混凝土之一 子类。spring-doc.cadn.net.cn

3.5.3.TestExecutionListener配置

Spring 提供了以下TestExecutionListener已注册的 implementations 默认情况下,完全按以下顺序:spring-doc.cadn.net.cn

注册TestExecutionListener实现

您可以注册TestExecutionListenerimplementation 的 子类及其嵌套类。@TestExecutionListeners注解。请参阅注释支持和 javadoc 以获取@TestExecutionListeners了解详细信息和示例。spring-doc.cadn.net.cn

切换到默认TestExecutionListener实现

如果您扩展了一个注解有@TestExecutionListeners你需要 切换到使用默认的侦听器集,您可以使用 以后。spring-doc.cadn.net.cn

Java
// Switch to default listeners
@TestExecutionListeners(
    listeners = {},
    inheritListeners = false,
    mergeMode = MERGE_WITH_DEFAULTS)
class MyTest extends BaseTest {
    // class body...
}
Kotlin
// Switch to default listeners
@TestExecutionListeners(
    listeners = [],
    inheritListeners = false,
    mergeMode = MERGE_WITH_DEFAULTS)
class MyTest : BaseTest {
    // class body...
}
自动发现默认TestExecutionListener实现

注册TestExecutionListener使用@TestExecutionListeners是 适用于在有限测试场景中使用的自定义监听器。但是,它可以 如果需要在整个测试套件中使用自定义侦听器,则会变得很麻烦。这 此问题已通过支持自动发现默认得到解决TestExecutionListener通过SpringFactoriesLoader机制。spring-doc.cadn.net.cn

具体来说,spring-testmodule declars all core 默认TestExecutionListenerimplementation 在org.springframework.test.context.TestExecutionListener键入 其META-INF/spring.factoriesproperties 文件。第三方框架和开发人员 可以贡献自己的TestExecutionListenerimplementations 添加到 default 列表中 听众以同样的方式通过他们自己的META-INF/spring.factories性能 文件。spring-doc.cadn.net.cn

订购TestExecutionListener实现

当 TestContext 框架发现默认TestExecutionListener实现 通过上述 SpringFactoriesLoader机制中,实例化的侦听器通过使用 Spring的AnnotationAwareOrderComparator,它尊重 Spring 的Orderedinterface 和@Order注解进行排序。AbstractTestExecutionListener并且全部默认TestExecutionListenerSpring implements 提供的实现Ordered跟 适当的值。因此,第三方框架和开发人员应确保 他们的默认TestExecutionListener实现按正确的顺序注册 通过实施Ordered或声明@Order.请参阅 javadoc 以获取getOrder()核心 default 的方法TestExecutionListenerimplementations (实现) 详细了解 值将分配给每个核心侦听器。spring-doc.cadn.net.cn

合并TestExecutionListener实现

如果自定义TestExecutionListener通过@TestExecutionListeners这 默认侦听器未注册。在最常见的测试场景中,这有效地 强制开发人员手动声明所有默认侦听器以及任何自定义 听众。下面的清单演示了这种配置样式:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DirtiesContextBeforeModesTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
class MyTest {
    // class body...
}
Kotlin
@ContextConfiguration
@TestExecutionListeners(
    MyCustomTestExecutionListener::class,
    ServletTestExecutionListener::class,
    DirtiesContextBeforeModesTestExecutionListener::class,
    DependencyInjectionTestExecutionListener::class,
    DirtiesContextTestExecutionListener::class,
    TransactionalTestExecutionListener::class,
    SqlScriptsTestExecutionListener::class
)
class MyTest {
    // class body...
}

这种方法的挑战在于它要求开发人员确切地知道 默认注册哪些侦听器。此外,默认侦听器集可以 从一个版本更改为另一个版本 — 例如,SqlScriptsTestExecutionListener是 在 Spring Framework 4.1 中引入,以及DirtiesContextBeforeModesTestExecutionListener是在 Spring Framework 4.2 中引入的。此外,像 Spring 这样的第三方框架 Boot 和 Spring Security 注册自己的默认值TestExecutionListener使用上述自动发现机制实现。spring-doc.cadn.net.cn

为避免必须了解并重新声明所有默认侦听器,您可以设置mergeMode属性@TestExecutionListenersMergeMode.MERGE_WITH_DEFAULTS.MERGE_WITH_DEFAULTS指示本地声明的侦听器应与 default 侦听器。合并算法可确保从 list 中,并且生成的合并侦听器集根据语义进行排序 之AnnotationAwareOrderComparator,如订购TestExecutionListener实现. 如果侦听器实现了Ordered或带有@Order,它会影响 位置,它与默认值合并。否则,本地声明的侦听器 在合并时附加到默认侦听器列表中。spring-doc.cadn.net.cn

例如,如果MyCustomTestExecutionListener类 配置其order值(例如500) 小于ServletTestExecutionListener(恰好是1000)、MyCustomTestExecutionListener然后,可以自动与 defaults 位于ServletTestExecutionListener,而前面的示例可以 替换为以下内容:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}
Kotlin
@ContextConfiguration
@TestExecutionListeners(
        listeners = [MyCustomTestExecutionListener::class],
        mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}

3.5.4. 应用程序事件

从 Spring Framework 5.3.3 开始,TestContext 框架支持记录在ApplicationContext以便可以针对 测试。在执行单个测试期间发布的所有事件都可通过 这ApplicationEventsAPI 允许您将事件作为java.util.Stream.spring-doc.cadn.net.cn

要使用ApplicationEvents在测试中,执行以下作。spring-doc.cadn.net.cn

以下测试类使用SpringExtension用于 JUnit Jupiter 和 AssertJ 断言应用程序事件的类型 在 Spring 管理的组件中调用方法时发布:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {

    @Autowired
    OrderService orderService;

    @Autowired
    ApplicationEvents events; (2)

    @Test
    void submitOrder() {
        // Invoke method in OrderService that publishes an event
        orderService.submitOrder(new Order(/* ... */));
        // Verify that an OrderSubmitted event was published
        long numEvents = events.stream(OrderSubmitted.class).count(); (3)
        assertThat(numEvents).isEqualTo(1);
    }
}
1 使用@RecordApplicationEvents.
2 注入ApplicationEvents实例。
3 使用ApplicationEvents计算数量 APIOrderSubmitted事件被公布。
Kotlin
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {

    @Autowired
    lateinit var orderService: OrderService

    @Autowired
    lateinit var events: ApplicationEvents (2)

    @Test
    fun submitOrder() {
        // Invoke method in OrderService that publishes an event
        orderService.submitOrder(Order(/* ... */))
        // Verify that an OrderSubmitted event was published
        val numEvents = events.stream(OrderSubmitted::class).count() (3)
        assertThat(numEvents).isEqualTo(1)
    }
}
1 使用@RecordApplicationEvents.
2 注入ApplicationEvents实例。
3 使用ApplicationEvents计算数量 APIOrderSubmitted事件被公布。

请参阅ApplicationEventsJavadoc有关ApplicationEvents应用程序接口。spring-doc.cadn.net.cn

3.5.5. 测试执行事件

EventPublishingTestExecutionListener在 Spring Framework 5.2 中引入的 实现自定义的替代方法TestExecutionListener.组件 test 的ApplicationContext可以监听EventPublishingTestExecutionListener,每个 API 都对应于TestExecutionListener应用程序接口。spring-doc.cadn.net.cn

这些事件可能由于各种原因而被使用,例如重置 mock bean 或跟踪 测试执行。使用测试执行事件而不是实现 自定义TestExecutionListener是测试执行事件可以被任何 在测试中注册的 Spring beanApplicationContext,这样的 bean 可能会受益 直接从依赖项注入和ApplicationContext.在 contrast 中,一个TestExecutionListener不是ApplicationContext.spring-doc.cadn.net.cn

EventPublishingTestExecutionListener默认已注册;但是,它只是 如果ApplicationContext已加载。这可以防止ApplicationContext避免不必要地加载或过早加载。spring-doc.cadn.net.cn

因此,一个BeforeTestClassEvent只有在ApplicationContext已被另一个TestExecutionListener.例如,使用 默认的TestExecutionListenerimplementations registered,则BeforeTestClassEvent不会为使用 特定测试ApplicationContext,但BeforeTestClassEvent 发布 同一测试套件中使用同一测试的任何后续测试类ApplicationContext因为在后续测试 类运行(只要上下文尚未从ContextCache通过@DirtiesContext或 max-size 驱逐策略)。spring-doc.cadn.net.cn

如果您希望确保BeforeTestClassEvent始终针对每个测试发布 类中,您需要注册一个TestExecutionListener加载ApplicationContextbeforeTestClasscallback 和TestExecutionListener必须在EventPublishingTestExecutionListener.spring-doc.cadn.net.cn

同样,如果@DirtiesContext用于删除ApplicationContext从 上下文缓存AfterTestClassEvent不会针对该测试类发布。spring-doc.cadn.net.cn

为了监听测试执行事件,Spring bean 可以选择实现org.springframework.context.ApplicationListener接口。或者,侦听器 方法可以使用@EventListener并配置为侦听 上面列出的特定事件类型(请参阅基于 Comments 的事件侦听器)。 由于这种方法的流行, Spring 提供了以下专用的@EventListener注解来简化测试执行事件侦听器的注册。 这些注释位于org.springframework.test.context.event.annotation包。spring-doc.cadn.net.cn

异常处理

默认情况下,如果测试执行事件侦听器在使用 事件,该异常将传播到正在使用的底层测试框架(例如 JUnit 或 TestNG)。例如,如果BeforeTestMethodEvent结果 异常,则相应的测试方法将因异常而失败。在 相反,如果异步测试执行事件侦听器引发异常,则 exception 不会传播到底层测试框架。有关更多详细信息 异步异常处理,请参阅类级 javadoc 以获取@EventListener.spring-doc.cadn.net.cn

异步侦听器

如果您希望特定的测试执行事件侦听器异步处理事件, 您可以使用 Spring 的定期@Async支持.有关更多详细信息,请参阅类级 javadoc 以获取@EventListener.spring-doc.cadn.net.cn

3.5.6. 上下文管理

TestContext为测试实例提供上下文管理和缓存支持 它负责。测试实例不会自动获得对 配置ApplicationContext.但是,如果测试类实现ApplicationContextAwareinterface 中,引用ApplicationContext供给 添加到测试实例中。请注意,AbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTests实现ApplicationContextAware因此, 提供对ApplicationContext自然而然。spring-doc.cadn.net.cn

@Autowired ApplicationContext

作为实现ApplicationContextAware接口中,您可以注入 测试类的应用程序上下文,通过@Autowired注释 Field 或 setter 方法,如下例所示:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    ApplicationContext applicationContext;

    // class body...
}
1 注入ApplicationContext.
Kotlin
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    lateinit var applicationContext: ApplicationContext

    // class body...
}
1 注入ApplicationContext.

同样,如果您的测试配置为加载WebApplicationContext,您可以注入 Web 应用程序上下文添加到测试中,如下所示:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    WebApplicationContext wac;

    // class body...
}
1 配置WebApplicationContext.
2 注入WebApplicationContext.
Kotlin
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    lateinit var wac: WebApplicationContext
    // class body...
}
1 配置WebApplicationContext.
2 注入WebApplicationContext.

使用 Dependency injection@AutowiredDependencyInjectionTestExecutionListener,这是默认配置的 (参见 Dependency Injection of Test Fixtures)。spring-doc.cadn.net.cn

使用 TestContext 框架的测试类不需要扩展任何特定的 类或实现特定接口来配置其应用程序上下文。相反 配置是通过声明@ContextConfiguration注解在 类级别。如果您的测试类未显式声明应用程序上下文资源 locations 或组件类中,配置的ContextLoader确定如何加载 context 从默认位置或默认配置类。除了上下文 资源位置和组件类,也可以配置应用程序上下文 通过 Application Context Initializers 进行初始化。spring-doc.cadn.net.cn

以下部分解释了如何使用 Spring 的@ContextConfigurationannotation 添加到 配置测试ApplicationContext通过使用 XML 配置文件、Groovy 脚本、 组件类(通常@Configuration类)或上下文初始值设定项。 或者,您可以实施和配置自己的自定义SmartContextLoader为 高级用例。spring-doc.cadn.net.cn

使用 XML 资源的上下文配置

要加载ApplicationContext对于使用 XML 配置文件的测试,请注释 你的 test 类与@ContextConfiguration并配置locations属性替换为 一个包含 XML 配置元数据的资源位置的数组。普通或 相对路径(例如,context.xml) 被视为类路径资源,即 相对于定义测试类的 package。以斜杠开头的路径 被视为绝对 Classpath 位置(例如/org/example/config.xml).一个 path 表示资源 URL(即前缀为classpath:,file:,http:等)按原样使用。spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
1 将 locations 属性设置为 XML 文件列表。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
1 将 locations 属性设置为 XML 文件列表。

@ContextConfiguration支持locations属性通过 标准 Javavalue属性。因此,如果您不需要声明额外的 属性@ContextConfiguration,您可以省略locationsattribute name 并使用简写格式声明资源位置 以下示例演示:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
1 指定 XML 文件而不使用location属性。
Kotlin
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
1 指定 XML 文件而不使用location属性。

如果同时省略locationsvalue属性@ContextConfiguration注解中,TestContext 框架会尝试检测默认的 XML 资源位置。具体说来GenericXmlContextLoaderGenericXmlWebContextLoader根据测试名称检测默认位置 类。如果您的类名为com.example.MyTest,GenericXmlContextLoader加载您的 application context from"classpath:com/example/MyTest-context.xml".以下内容 示例展示了如何做到这一点:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 从默认位置加载配置。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 从默认位置加载配置。
使用 Groovy 脚本进行上下文配置

要加载ApplicationContext对于您的测试,您可以使用使用 Groovy Bean 定义 DSL 的 Groovy 脚本进行注释 你的 test 类与@ContextConfiguration并配置locationsvalue属性替换为包含 Groovy 脚本资源位置的数组。资源 Groovy 脚本的查找语义与 XML 配置文件中描述的相同。spring-doc.cadn.net.cn

启用 Groovy 脚本支持
支持使用 Groovy 脚本加载ApplicationContext在Spring 如果 Groovy 在 Classpath 上,则会自动启用 TestContext Framework。

下面的示例展示了如何指定 Groovy 配置文件:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
    // class body...
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") (1)
class MyTest {
    // class body...
}
1 指定 Groovy 配置文件的位置。

如果同时省略locationsvalue属性@ContextConfiguration注解时,TestContext 框架会尝试检测默认的 Groovy 脚本。 具体说来GenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoader根据测试类的名称检测默认位置。如果您的类名为com.example.MyTest中,Groovy 上下文加载器会从"classpath:com/example/MyTestContext.groovy".以下示例演示如何使用 默认值:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 从默认位置加载配置。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 从默认位置加载配置。
同时声明 XML 配置和 Groovy 脚本

您可以使用 这locationsvalue属性@ContextConfiguration.如果 配置的资源位置以.xml,它是通过使用XmlBeanDefinitionReader.否则,它将使用GroovyBeanDefinitionReader.spring-doc.cadn.net.cn

下面的清单显示了如何在集成测试中将两者结合起来:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
class MyTest {
    // class body...
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration("/app-config.xml", "/TestConfig.groovy")
class MyTest {
    // class body...
}
使用组件类的上下文配置

要加载ApplicationContext对于使用组件类的测试(请参阅基于 Java 的容器配置),您可以对测试 class 替换为@ContextConfiguration并配置classes属性替换为数组 ,其中包含对组件类的引用。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
    // class body...
}
1 指定组件类。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) (1)
class MyTest {
    // class body...
}
1 指定组件类。
组件类

术语 “component class” 可以指以下任何内容:spring-doc.cadn.net.cn

请参阅 javadoc@Configuration@Bean了解更多信息 关于组件类的配置和语义,请特别注意 到讨论@BeanLite 模式。spring-doc.cadn.net.cn

如果省略classes属性@ContextConfigurationannotation、 TestContext 框架尝试检测是否存在默认配置类。 具体说来AnnotationConfigContextLoaderAnnotationConfigWebContextLoader检测全部static满足 configuration 类实现,如@Configurationjavadoc 的 请注意,配置类的名称是任意的。此外,测试类可以 包含多个staticnested configuration 类。在以下 示例中,OrderServiceTestclass 声明一个static嵌套配置类 叫Config,它会自动用于加载ApplicationContext用于测试 类:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    OrderService orderService;

    @Test
    void testOrderService() {
        // test the orderService
    }

}
1 从嵌套的Config类。
Kotlin
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the nested Config class
class OrderServiceTest {

    @Autowired
    lateinit var orderService: OrderService

    @Configuration
    class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        fun orderService(): OrderService {
            // set properties, etc.
            return OrderServiceImpl()
        }
    }

    @Test
    fun testOrderService() {
        // test the orderService
    }
}
1 从嵌套的Config类。
混合 XML、Groovy 脚本和组件类

有时可能需要混合使用 XML 配置文件、Groovy 脚本和 组件类(通常@Configuration类)配置ApplicationContext用于您的测试。例如,如果您在 production 中,您可以决定要使用@Configuration要配置的类 特定的 Spring Management 组件,反之亦然。spring-doc.cadn.net.cn

此外,一些第三方框架(例如 Spring Boot)提供了一流的 支持加载ApplicationContext来自不同类型的资源 同时(例如,XML 配置文件、Groovy 脚本和@Configuration类)。Spring 框架历史上不支持此功能 标准部署。因此,大多数SmartContextLoader实现 Spring Framework 在spring-testmodule 仅支持一种资源类型 对于每个测试上下文。但是,这并不意味着您不能同时使用两者。一 一般规则的例外情况是GenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoader支持 XML 配置文件和 Groovy 脚本。此外,第三方框架可以选择支持 两者的声明locationsclasses通过@ContextConfiguration和 TestContext 框架中的标准测试支持,您有以下选项。spring-doc.cadn.net.cn

如果要使用资源位置(例如,XML 或 Groovy),并且@Configuration类来配置测试,则必须选择一个作为入口点,并且该类必须 include 或 import other。例如,在 XML 或 Groovy 脚本中,您可以包含@Configuration类,通过使用组件扫描或将它们定义为普通 Spring beans 的@Configuration类中,你可以使用@ImportResource导入 XML 配置文件或 Groovy 脚本。请注意,此行为在语义上是等效的 了解如何在生产环境中配置应用程序:在生产配置中,您 定义一组 XML 或 Groovy 资源位置或一组@Configuration您的生产从中ApplicationContext已加载,但您仍然拥有 自由包含或导入其他类型的配置。spring-doc.cadn.net.cn

使用 Context Initializers 进行 Context Configuration

要配置ApplicationContext对于使用上下文初始值设定项的测试, 使用@ContextConfiguration并配置initializers属性,其中包含对实现ApplicationContextInitializer.然后,使用声明的上下文初始值设定项 初始化ConfigurableApplicationContext为您的测试加载。请注意, 混凝土ConfigurableApplicationContext每个声明的初始化器支持的类型 必须与ApplicationContextSmartContextLoader正在使用(通常为GenericApplicationContext).此外, 初始化器的调用顺序取决于它们是否实现 Spring 的Ordered接口或用 Spring 的@Orderannotation 或标准@Priority注解。以下示例演示如何使用初始值设定项:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) (1)
class MyTest {
    // class body...
}
1 使用配置类和初始值设定项指定配置。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
        classes = [TestConfig::class],
        initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
    // class body...
}
1 使用配置类和初始值设定项指定配置。

您还可以省略 XML 配置文件、Groovy 脚本或 组件类@ContextConfiguration完全,而是仅声明ApplicationContextInitializer类,然后负责注册 bean 在上下文中 — 例如,通过以编程方式从 XML 加载 Bean 定义 文件或配置类。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
    // class body...
}
1 仅使用初始值设定项指定配置。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
    // class body...
}
1 仅使用初始值设定项指定配置。
上下文配置继承

@ContextConfiguration支持布尔值inheritLocationsinheritInitializers表示资源位置还是组件类和上下文的属性 超类声明的初始化器应该被继承。两者的默认值 flags 是true.这意味着测试类继承资源位置或 组件类以及任何超类声明的上下文初始化器。 具体来说,将附加测试类的资源位置或组件类 添加到由 Superclasses 声明的资源位置或带注释的类的列表。 同样,给定测试类的初始化器将添加到初始化器集中 由 test superclasses 定义。因此,子类可以选择扩展资源 locations、Component classes 或 Context Initializers 的 Initializer 进行初始化。spring-doc.cadn.net.cn

如果inheritLocationsinheritInitializers属性@ContextConfiguration设置为false、资源位置或组件类以及上下文 initializers 分别用于测试类 shadow 并有效地替换 由超类定义的配置。spring-doc.cadn.net.cn

从 Spring Framework 5.3 开始,测试配置也可以从封闭中继承 类。看@Nestedtest 类配置了解详情。

在下一个使用 XML 资源位置的示例中,ApplicationContextExtendedTest加载自base-config.xmlextended-config.xml,按此顺序。 在extended-config.xml因此,可以覆盖(即替换)这些 定义于base-config.xml.下面的示例展示了一个类如何扩展 another 并使用自己的配置文件和 superclass 的配置文件:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 配置文件。
2 配置文件。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 配置文件。
2 配置文件。

同样,在下一个使用组件类的示例中,ApplicationContextExtendedTestBaseConfigExtendedConfig类,因为 次序。在ExtendedConfig因此,可以覆盖 (即 replace) 在BaseConfig.下面的示例展示了一个类如何扩展 另一个,并使用自己的 Configuration 类和 Superclass 的 Configuration 类:spring-doc.cadn.net.cn

Java
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 Configuration 类。
2 在子类中定义的 Configuration 类。
Kotlin
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig::class) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 Configuration 类。
2 在子类中定义的 Configuration 类。

在下一个使用上下文初始值设定项的示例中,ApplicationContextExtendedTest使用BaseInitializerExtendedInitializer.注意 但是,调用初始值设定项的顺序取决于它们是否 实现 Spring 的Ordered接口或用 Spring 的@Order注解 或标准@Priority注解。以下示例显示了一个类如何 扩展另一个 Initializer 并同时使用它自己的 Initializer 和 Superclass 的 Initializer:spring-doc.cadn.net.cn

Java
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 Initializer 定义在超类中。
2 Initializer 在子类中定义。
Kotlin
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 Initializer 定义在超类中。
2 Initializer 在子类中定义。
使用环境配置文件进行上下文配置

Spring 框架对环境和配置文件的概念具有一流的支持 (又名“bean 定义配置文件”),并且可以配置集成测试以激活 用于各种测试场景的特定 bean 定义配置文件。这是通过以下方式实现的 使用@ActiveProfiles注解并提供 在加载ApplicationContext进行测试。spring-doc.cadn.net.cn

您可以使用@ActiveProfilesSmartContextLoaderSPI 的 API 和@ActiveProfiles不支持较旧的ContextLoaderSPI 的 API 中。

考虑两个包含 XML 配置和@Configuration类:spring-doc.cadn.net.cn

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script
                location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>

    <beans profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

什么时候TransferServiceTest运行,则其ApplicationContextapp-config.xml配置文件。如果您检查app-config.xml中,可以看到accountRepositoryBean 依赖于dataSource豆。然而dataSource未定义为顶级 Bean。相反dataSource定义了三次:在production配置文件中,在dev轮廓 在default轮廓。spring-doc.cadn.net.cn

通过注释TransferServiceTest@ActiveProfiles("dev"),我们指示 Spring TestContext 框架加载ApplicationContext,并将 Active Profiles 设置为{"dev"}.因此,将创建一个嵌入式数据库,并使用测试数据填充,并且 这accountRepositorybean 与开发DataSource. 这可能是我们在集成测试中想要的。spring-doc.cadn.net.cn

有时,将 bean 分配给default轮廓。默认值内的 Bean 仅当没有专门激活其他配置文件时,才会包含配置文件。您可以使用 这是为了定义要在应用程序的默认状态中使用的 “fallback” bean。为 例如,您可以显式地为devproduction配置 文件 但是,当内存中数据源都未处于活动状态时,请将内存中数据源定义为默认值。spring-doc.cadn.net.cn

以下代码清单演示了如何实现相同的配置和 集成测试@Configurationclasses 而不是 XML:spring-doc.cadn.net.cn

Java
@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("dev")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
Java
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
Kotlin
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
Java
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}
Java
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }
}
Kotlin
@Configuration
class TransferServiceConfig {

    @Autowired
    lateinit var dataSource: DataSource

    @Bean
    fun transferService(): TransferService {
        return DefaultTransferService(accountRepository(), feePolicy())
    }

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun feePolicy(): FeePolicy {
        return ZeroFeePolicy()
    }
}
Java
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

在这个变体中,我们将 XML 配置拆分为四个独立的@Configuration类:spring-doc.cadn.net.cn

与基于 XML 的配置示例一样,我们仍然对TransferServiceTest@ActiveProfiles("dev"),但这次我们通过 使用@ContextConfiguration注解。测试类本身的主体仍然存在 完全没有改变。spring-doc.cadn.net.cn

通常情况下,在多个测试类中使用一组配置文件 在给定项目中。因此,为了避免@ActiveProfiles注解中,你可以声明@ActiveProfilesonce on 基类和 subclasses 自动继承@ActiveProfiles配置。在 以下示例中,声明@ActiveProfiles(以及其他注释) 已移动到抽象超类AbstractIntegrationTest:spring-doc.cadn.net.cn

从 Spring Framework 5.3 开始,测试配置也可以从封闭中继承 类。看@Nestedtest 类配置了解详情。
Java
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Kotlin
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Java
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

@ActiveProfiles还支持inheritProfiles可用于 禁用活动配置文件的继承,如下例所示:spring-doc.cadn.net.cn

Java
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // test body
}
Kotlin
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
    // test body
}

此外,有时需要解析测试的活动配置文件 以编程方式而不是以声明方式 — 例如,基于:spring-doc.cadn.net.cn

要以编程方式解析活动的 Bean 定义配置文件,您可以实现 自定义ActiveProfilesResolver并使用resolver属性@ActiveProfiles.有关更多信息,请参阅相应的 javadoc。 以下示例演示了如何实现和注册自定义OperatingSystemActiveProfilesResolver:spring-doc.cadn.net.cn

Java
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver.class,
        inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
Kotlin
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver::class,
        inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
    // test body
}
Java
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    public String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}
Kotlin
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {

    override fun resolve(testClass: Class<*>): Array<String> {
        val profile: String = ...
        // determine the value of profile based on the operating system
        return arrayOf(profile)
    }
}
使用测试属性源的上下文配置

Spring 框架对环境的概念具有一流的支持,其中 属性源的层次结构,并且您可以使用特定于 Test 的 property 源。与@PropertySource注释使用的@Configuration类中,您可以声明@TestPropertySource测试上的注释 类来声明测试属性文件或内联属性的资源位置。 这些测试属性源将添加到PropertySourcesEnvironment对于ApplicationContextloaded 进行带注释的集成测试。spring-doc.cadn.net.cn

您可以使用@TestPropertySourceSmartContextLoaderSPI 的 API 和@TestPropertySource不支持较旧的ContextLoaderSPI 的 API 中。spring-doc.cadn.net.cn

的实现SmartContextLoader访问合并的 Test Property Source 值 通过getPropertySourceLocations()getPropertySourceProperties()methods 中的MergedContextConfiguration.spring-doc.cadn.net.cn

声明测试属性源

您可以使用locationsvalue属性@TestPropertySource.spring-doc.cadn.net.cn

支持传统和基于 XML 的属性文件格式,例如,"classpath:/com/example/test.properties""file:///path/to/file.xml".spring-doc.cadn.net.cn

每个路径都解释为一个 SpringResource.普通路径(例如"test.properties") 被视为相对于 package 的 Classpath 资源 其中定义了测试类。以斜杠开头的路径被视为 绝对 Classpath 资源(例如:"/org/example/test.xml").一条 引用一个 URL(例如,前缀为classpath:,file:http:) 是 使用指定的资源协议加载。资源位置通配符(例如*/.properties) 不允许:每个位置必须恰好评估为 1.properties.xml资源。spring-doc.cadn.net.cn

以下示例使用测试属性文件:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 指定具有绝对路径的属性文件。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 指定具有绝对路径的属性文件。

您可以使用properties属性@TestPropertySource,如下例所示。都 键值对被添加到封闭的Environment作为单个测试PropertySource具有最高优先级。spring-doc.cadn.net.cn

键值对支持的语法与为 Java 属性文件:spring-doc.cadn.net.cn

下面的示例设置两个内联属性:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) (1)
class MyIntegrationTests {
    // class body...
}
1 使用键值语法的两种变体设置两个属性。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
1 使用键值语法的两种变体设置两个属性。

从 Spring Framework 5.2 开始,@TestPropertySource可用作可重复注释。 这意味着你可以有多个@TestPropertySource在单个 test 类,其中locationsproperties从以后@TestPropertySource注释覆盖以前的注释@TestPropertySource附注。spring-doc.cadn.net.cn

此外,您可以在一个测试类上声明多个组合注释,每个注释都是 元注释@TestPropertySource和所有这些@TestPropertySource声明将有助于您的测试属性源。spring-doc.cadn.net.cn

直接存在@TestPropertySource注释始终优先于 元现在@TestPropertySource附注。换句话说,locationsproperties来自直接存在@TestPropertySourceannotation 将覆盖locationsproperties@TestPropertySource用作 meta-annotation 中。spring-doc.cadn.net.cn

默认属性文件检测

如果@TestPropertySource声明为空注解(即,没有显式 的值locationsproperties属性),则尝试检测 default 属性文件。例如 如果带注释的测试类为com.example.MyTest、相应的默认属性 file 为classpath:com/example/MyTest.properties.如果无法检测到默认值,则IllegalStateException被抛出。spring-doc.cadn.net.cn

优先

测试属性的优先级高于作系统的 环境、Java 系统属性或应用程序添加的属性源 通过使用@PropertySource或以编程方式。因此,测试属性可以 用于选择性地覆盖从 System 和 Application 属性加载的属性 来源。此外,内联属性的优先级高于加载的属性 从资源位置。但是请注意,通过@DynamicPropertySource有 比通过@TestPropertySource.spring-doc.cadn.net.cn

在下一个示例中,timezoneport属性以及"/test.properties"覆盖 System 中定义的任何同名属性 和应用程序属性源。此外,如果"/test.properties"file 定义 条目的timezoneport属性被内联的 使用properties属性。以下示例显示了如何作 要在 File 和 Inline 中指定属性:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // class body...
}
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties",
        properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
    // class body...
}
继承和覆盖测试属性源

@TestPropertySource支持布尔值inheritLocationsinheritProperties属性,这些属性表示属性文件的资源位置是否内联 超类声明的属性应该被继承。这两个标志的默认值 是true.这意味着测试类继承 locations 和 inlined 属性 由任何超类声明。具体来说,一个 test 类附加到 superclasses 声明的 locations 和 inlined 属性中。 因此,子类可以选择扩展 locations 和 inlined 属性。注意 稍后出现的属性会隐藏(即 override)同名的属性 出现得更早。此外,上述优先规则也适用于继承的 test 属性源。spring-doc.cadn.net.cn

如果inheritLocationsinheritProperties属性@TestPropertySource是 设置为false、 locations 或 inlined 属性,分别为 test 类 shadow 并有效地替换 superclasses 定义的配置。spring-doc.cadn.net.cn

从 Spring Framework 5.3 开始,测试配置也可以从封闭中继承 类。看@Nestedtest 类配置了解详情。

在下一个示例中,ApplicationContextBaseTest仅使用base.propertiesfile 作为测试属性源。相比之下,ApplicationContextExtendedTest使用base.propertiesextended.properties文件作为测试属性源位置。以下示例显示了如何定义 子类及其超类中的属性properties文件:spring-doc.cadn.net.cn

Java
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}

在下一个示例中,ApplicationContextBaseTest仅使用 内联key1财产。相比之下,ApplicationContextExtendedTest是 使用内联key1key2性能。以下示例显示了如何作 要使用 inline properties 在 subclass 及其 superclass 中定义 properties:spring-doc.cadn.net.cn

Java
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}
使用动态属性源的上下文配置

从 Spring Framework 5.2.5 开始,TestContext 框架通过@DynamicPropertySource注解。此注释可用于 需要将具有动态值的属性添加到集合PropertySourcesEnvironment对于ApplicationContextloaded 的 集成测试。spring-doc.cadn.net.cn

@DynamicPropertySourceAnnotation 及其支持基础设施是 最初设计为允许基于 Testcontainers 的测试中的属性很容易地暴露给 Spring 集成测试。但是,此功能也可以与任何形式的 外部资源,其生命周期在测试的ApplicationContext.spring-doc.cadn.net.cn

@TestPropertySource注解,以及@DynamicPropertySource必须申请 更改为static方法,该方法接受单个DynamicPropertyRegistry参数,即 用于将名称-值对添加到Environment.值是动态的,并通过 一个Supplier仅当解析属性时,才会调用该属性。通常,方法 引用用于提供值,如以下示例所示,该示例使用 Testcontainers 项目来管理 Spring 之外的 Redis 容器ApplicationContext.托管 Redis 容器的 IP 地址和端口已创建 可用于测试的ApplicationContext通过redis.hostredis.port性能。这些属性可以通过 Spring 的Environment抽象或直接注入到 Spring 管理的组件中 —— 例如,通过@Value("${redis.host}")@Value("${redis.port}")分别。spring-doc.cadn.net.cn

如果您使用@DynamicPropertySource在基类中,并发现子类中的测试 失败,因为 Dynamic 属性在子类之间发生了变化,你可能需要注解 你的基类替换为@DirtiesContext自 确保每个子类都有自己的ApplicationContext使用正确的动态 性能。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    @Container
    static GenericContainer redis =
        new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        registry.add("redis.host", redis::getHost);
        registry.add("redis.port", redis::getFirstMappedPort);
    }

    // tests ...

}
Kotlin
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    companion object {

        @Container
        @JvmStatic
        val redis: GenericContainer =
            GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379)

        @DynamicPropertySource
        @JvmStatic
        fun redisProperties(registry: DynamicPropertyRegistry) {
            registry.add("redis.host", redis::getHost)
            registry.add("redis.port", redis::getFirstMappedPort)
        }
    }

    // tests ...

}
优先

动态属性的优先级高于从@TestPropertySource, 作系统的环境、Java 系统属性或由 应用程序使用@PropertySource或以编程方式。因此 动态属性可用于选择性地覆盖通过@TestPropertySource、系统属性源和应用程序属性源。spring-doc.cadn.net.cn

加载一个WebApplicationContext

要指示 TestContext 框架加载WebApplicationContext而不是 标准ApplicationContext中,您可以使用@WebAppConfiguration.spring-doc.cadn.net.cn

存在@WebAppConfiguration在你的测试类上,指示 TestContext 框架 (TCF) 中,一个WebApplicationContext(WAC) 应为您的 集成测试。在后台,TCF 确保MockServletContext是 创建并提供给测试的 WAC。默认情况下,您的MockServletContext设置为src/main/webapp.这被解释为路径相对 添加到 JVM 的根目录(通常是项目的路径)。如果您熟悉 Maven 项目中 Web 应用程序的目录结构,您知道src/main/webapp是 WAR 根目录的默认位置。如果您需要 覆盖此默认值,则可以提供@WebAppConfiguration注解(例如@WebAppConfiguration("src/test/webapp")).如果您愿意 从 Classpath 而不是文件系统中引用基本资源路径,您可以使用 Spring的classpath:前缀。spring-doc.cadn.net.cn

请注意,Spring 对WebApplicationContextimplementations 相当 支持标准ApplicationContext实现。当使用WebApplicationContext,您可以自由声明 XML 配置文件、Groovy 脚本、 或@Configuration@ContextConfiguration.您也可以免费使用 任何其他测试注解,例如@ActiveProfiles,@TestExecutionListeners,@Sql,@Rollback等。spring-doc.cadn.net.cn

本节中的其余示例显示了 加载一个WebApplicationContext.以下示例显示了 TestContext 框架对约定优于配置的支持:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}

如果使用@WebAppConfiguration未指定资源 base path 时,资源路径实际上默认为file:src/main/webapp.同样地 如果您声明@ContextConfiguration不指定 resourcelocations元件classes或上下文initializers时,Spring 会尝试检测是否存在 使用约定(即WacTests-context.xml在同一包中 作为WacTests类或静态嵌套@Configuration类)。spring-doc.cadn.net.cn

以下示例显示了如何使用@WebAppConfiguration以及一个 XML 资源位置,其中包含@ContextConfiguration:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}

这里需要注意的重要一点是具有这两个的 paths 的不同语义 附注。默认情况下,@WebAppConfiguration资源路径基于文件系统, 而@ContextConfiguration资源位置是基于 Classpath 的。spring-doc.cadn.net.cn

下面的示例显示,我们可以覆盖两者的默认资源语义 注解:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}

将此示例中的注释与上一个示例进行对比。spring-doc.cadn.net.cn

使用 Web Mock

为了提供全面的 Web 测试支持,TestContext 框架有一个ServletTestExecutionListener默认情况下处于启用状态。当针对WebApplicationContextTestExecutionListener使用 Spring Web 的RequestContextHolder以前 每个测试方法并创建一个MockHttpServletRequest一个MockHttpServletResponse和 一个ServletWebRequest基于配置了@WebAppConfiguration.ServletTestExecutionListener还可以确保MockHttpServletResponseServletWebRequest可以注入到测试实例中, 并且,一旦测试完成,它就会清理线程本地状态。spring-doc.cadn.net.cn

一旦你有了WebApplicationContextloaded 进行测试时,您可能会发现 需要与 Web mock 进行交互 — 例如,设置测试夹具或 在调用 Web 组件后执行断言。以下示例显示了 mock 可以自动连接到你的测试实例中。请注意,WebApplicationContextMockServletContext都缓存在测试套件中,而其他 mock 是 按测试方法由ServletTestExecutionListener.spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}
Kotlin
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    lateinit var wac: WebApplicationContext // cached

    @Autowired
    lateinit var servletContext: MockServletContext // cached

    @Autowired
    lateinit var session: MockHttpSession

    @Autowired
    lateinit var request: MockHttpServletRequest

    @Autowired
    lateinit var response: MockHttpServletResponse

    @Autowired
    lateinit var webRequest: ServletWebRequest

    //...
}
上下文缓存

一旦 TestContext 框架加载了一个ApplicationContext(或WebApplicationContext) 对于测试,该上下文将被缓存并重新用于所有声明 同一测试套件中相同的唯一上下文配置。了解如何缓存 有效,理解 “unique” 和 “test suite” 的含义非常重要。spring-doc.cadn.net.cn

ApplicationContext可以通过配置的组合来唯一标识 参数。因此,配置的独特组合 parameters 用于生成缓存上下文的 key。The TestContext 框架使用以下配置参数来构建上下文缓存键:spring-doc.cadn.net.cn

例如,如果TestClassA指定{"app-config.xml", "test-config.xml"}对于locations(或value) 属性的@ContextConfiguration、TestContext 框架 加载相应的ApplicationContext并将其存储在static上下文缓存 在仅基于这些位置的键下。因此,如果TestClassB还定义了{"app-config.xml", "test-config.xml"}对于其位置(显式或 隐式地通过继承),但未定义@WebAppConfiguration,则ContextLoader、不同的活动配置文件、不同的上下文初始化器、不同的 test 属性源,或者不同的父上下文,则相同的ApplicationContext由两个测试类共享。这意味着加载应用程序的设置成本 context 仅发生一次(每个测试套件),并且后续测试执行很多 更快。spring-doc.cadn.net.cn

测试套件和分叉流程

Spring TestContext 框架将应用程序上下文存储在静态缓存中。这 意味着上下文实际上存储在static变量。换句话说,如果 测试在单独的进程中运行,则在每个测试之间清除静态缓存 execution,这实际上会禁用缓存机制。spring-doc.cadn.net.cn

要从缓存机制中受益,所有测试都必须在同一进程或测试中运行 套房。这可以通过在 IDE 中作为一个组执行所有测试来实现。同样地 使用 Ant、Maven 或 Gradle 等构建框架执行测试时,它是 请务必确保 Build Framework 不会在测试之间分叉。例如 如果forkMode对于 Maven Surefire 插件设置为alwayspertest、TestContext 框架 无法在测试类之间缓存应用程序上下文,并且构建过程运行 因此,速度明显更慢。spring-doc.cadn.net.cn

上下文缓存的大小以默认最大大小 32 为界。每当 达到最大大小,则使用最近最少使用 (LRU) 的驱逐策略进行驱逐,并且 关闭过时的上下文。您可以从命令行或内部版本配置最大大小 script 中,通过设置名为spring.test.context.cache.maxSize.作为 或者,您可以通过SpringProperties机制。spring-doc.cadn.net.cn

由于在给定的测试套件中加载了大量的应用程序上下文 导致套件需要不必要地长时间运行,这通常是有益的 确切地知道已经加载和缓存了多少个上下文。要查看 底层上下文缓存中,您可以为org.springframework.test.context.cachelogging 类别设置为DEBUG.spring-doc.cadn.net.cn

在不太可能的情况下,测试会损坏应用程序上下文并需要重新加载 (例如,通过修改 Bean 定义或应用程序对象的状态),则 可以使用@DirtiesContext(参见@DirtiesContextin 春季测试 注释)。这指示 Spring 从缓存中删除上下文并重新构建 运行需要相同应用程序的下一个测试之前的应用程序上下文 上下文。请注意,对@DirtiesContext注解由DirtiesContextBeforeModesTestExecutionListenerDirtiesContextTestExecutionListener,默认情况下处于启用状态。spring-doc.cadn.net.cn

ApplicationContext 生命周期和控制台日志记录

当你需要调试使用 Spring TestContext 框架执行的测试时,它可以 可用于分析控制台输出(即输出到SYSOUTSYSERRstreams)。一些构建工具和 IDE 能够将控制台输出与给定的 测试;但是,某些控制台输出不能轻易地与给定的测试相关联。spring-doc.cadn.net.cn

关于由 Spring 框架本身或组件触发的控制台日志记录 在ApplicationContext,了解ApplicationContext它已被 Spring TestContext 框架加载到 测试套件。spring-doc.cadn.net.cn

ApplicationContextfor a test 通常在测试的实例 类 — 例如,在@Autowired字段。这意味着在 初始化ApplicationContext通常不能与 单个测试方法。但是,如果上下文在 根据 执行测试方法@DirtiesContextsemantics,上下文的新实例将在执行 测试方法。在后一种情况下,IDE 或构建工具可能会关联 使用单个测试方法进行控制台日志记录。spring-doc.cadn.net.cn

ApplicationContext,可以通过以下方案之一关闭测试。spring-doc.cadn.net.cn

如果上下文根据@DirtiesContext特定测试后的语义 方法,IDE 或构建工具可能会将控制台日志记录与 单个测试方法。如果上下文根据@DirtiesContext语义学 在测试类之后,在ApplicationContext不能与单个测试方法关联。同样,任何 在关闭阶段通过 JVM 关闭钩子触发的控制台日志记录不能是 与单个测试方法相关联。spring-doc.cadn.net.cn

当 SpringApplicationContext通过 JVM 关闭钩子关闭,执行回调 在关闭阶段,在名为SpringContextShutdownHook.所以 如果您希望禁用在ApplicationContext已关闭 通过 JVM 关闭钩子,您可以在日志记录中注册自定义过滤器 框架,该框架允许您忽略该线程启动的任何日志记录。spring-doc.cadn.net.cn

上下文层次结构

当编写依赖于加载的 Spring 的集成测试时ApplicationContext是的 通常足以针对单个上下文进行测试。但是,有时确实如此 对于针对ApplicationContext实例。例如,如果您正在开发 Spring MVC Web 应用程序,则通常 有一个根WebApplicationContext由 Spring 的ContextLoaderListener以及 孩子WebApplicationContext由 Spring 的DispatcherServlet.这会导致 父子上下文层次结构,其中共享组件和基础设施配置 在根上下文中声明,并在子上下文中由 Web 特定的 组件。另一个用例可以在 Spring Batch 应用程序中找到,您经常在其中 具有为共享批处理基础架构提供配置的父上下文,以及 子上下文,用于特定批处理作业的配置。spring-doc.cadn.net.cn

您可以通过声明 context 来编写使用上下文层次结构的集成测试 配置与@ContextHierarchy注解,或者在单个测试类上 或在测试类层次结构中。如果在多个类上声明了上下文层次结构 在 Test Class 层次结构中,您还可以合并或覆盖 Context Configuration 对于上下文层次结构中的特定命名级别。合并 层次结构中的给定级别,则配置资源类型(即 XML 配置 文件或组件类)必须一致。否则,完全可以接受 在使用不同的资源类型配置的上下文层次结构中具有不同的级别。spring-doc.cadn.net.cn

本节中其余基于 JUnit Jupiter 的示例显示了常见配置 需要使用上下文层次结构的集成测试的方案。spring-doc.cadn.net.cn

具有上下文层次结构的单个测试类

ControllerIntegrationTests表示 Spring MVC Web 应用程序,通过声明一个由两个级别组成的上下文层次结构, 一个用于根WebApplicationContext(使用TestAppConfig @Configuration类)和一个用于 Dispatcher Servlet 的WebApplicationContext(使用WebConfig @Configuration类)。这WebApplicationContext)是子上下文的 (即 层次结构中最低的上下文)。下面的清单显示了此配置场景:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

    @Autowired
    WebApplicationContext wac;

    // ...
}
Kotlin
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
    ContextConfiguration(classes = [TestAppConfig::class]),
    ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {

    @Autowired
    lateinit var wac: WebApplicationContext

    // ...
}
具有隐式父上下文的类层次结构

此示例中的测试类定义测试类中的上下文层次结构 等级制度。AbstractWebTests声明根的配置WebApplicationContext在 Spring 支持的 Web 应用程序中。但请注意,AbstractWebTests不声明@ContextHierarchy.因此,的子类AbstractWebTests可以选择参与上下文层次结构或遵循 的标准语义@ContextConfiguration.SoapWebServiceTestsRestWebServiceTests两者都扩展AbstractWebTests并通过以下方式定义上下文层次结构 用@ContextHierarchy.结果是加载了三个应用程序上下文(一个 对于每个声明@ContextConfiguration) 并加载应用程序上下文 根据AbstractWebTests设置为每个 为 Concrete 子类加载的上下文。下面的清单显示了这一点 配置场景:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests

@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()

@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
具有合并上下文层次结构配置的类层次结构

此示例中的类演示了命名层次结构级别的使用,以便将 context 层次结构中特定级别的配置。BaseTests定义两个级别 在层次结构中,parentchild.ExtendedTests延伸BaseTests并指示 Spring TestContext 框架来合并child层次结构级别,方法是确保name属性@ContextConfiguration都是child.结果是三个应用程序上下文 已加载:一个用于/app-config.xml,一个用于/user-config.xml,一个用于{"/user-config.xml", "/order-config.xml"}.与前面的示例一样, 应用程序上下文加载自/app-config.xml设置为 加载自/user-config.xml{"/user-config.xml", "/order-config.xml"}. 下面的清单显示了此配置场景:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
    ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
具有覆盖上下文层次结构配置的类层次结构

与前面的示例相比,此示例演示了如何覆盖 配置,方法是将inheritLocations标志输入@ContextConfigurationfalse.因此, 的应用程序上下文ExtendedTests仅从/test-user-config.xml和 将其父项设置为从中加载的上下文/app-config.xml.以下清单 显示了此配置方案:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
class ExtendedTests extends BaseTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
        ContextConfiguration(
                name = "child",
                locations = ["/test-user-config.xml"],
                inheritLocations = false
        ))
class ExtendedTests : BaseTests() {}
在上下文层次结构中弄脏上下文
如果您使用@DirtiesContext在上下文配置为 context 层次结构中,您可以使用hierarchyMode标志来控制上下文缓存 已清除。有关更多详细信息,请参阅@DirtiesContextSpring Testing Annotations@DirtiesContextjavadoc 的 Java 文档。

3.5.7. 测试 Fixture 的依赖注入

当您使用DependencyInjectionTestExecutionListener(由 default),则测试实例的依赖项将从 您配置了 application context@ContextConfiguration或相关 附注。您可以使用 setter 注入和/或字段注入,具体取决于 您选择哪些 Comments,以及是将它们放在 setter 方法还是 Fields 上。 如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入 (参见依赖项注入SpringExtension).为了与 Spring 的基于 Comments 的 injection 支持,你也可以使用 Spring 的@Autowiredannotation 或@Inject来自 JSR-330 的注解,用于 field 和 setter 注入。spring-doc.cadn.net.cn

对于 JUnit Jupiter 以外的测试框架,TestContext 框架不会 参与 Test 类的实例化。因此,使用@Autowired@Injectfor constructors 对测试类没有影响。
尽管在生产代码中不鼓励使用字段注入,但 实际上在测试代码中很自然。差异的基本原理是您将 永远不要直接实例化你的测试类。因此,无需能够 调用publicconstructor 或 setter 方法。

因为@Autowired用于执行自动装配 type,如果你有多个相同类型的 bean 定义,则不能依赖 this 方法。在这种情况下,您可以使用@Autowired在 结合@Qualifier.您也可以选择使用@Inject@Named.或者,如果您的测试类可以访问其ApplicationContext你 可以通过使用(例如)对applicationContext.getBean("titleRepository", TitleRepository.class).spring-doc.cadn.net.cn

如果您不希望将依赖项注入应用于测试实例,请不要注释 fields 或 setter 方法与@Autowired@Inject.或者,您可以禁用 依赖项注入,方法是使用@TestExecutionListeners并省略DependencyInjectionTestExecutionListener.class从侦听器列表中。spring-doc.cadn.net.cn

考虑测试HibernateTitleRepository类,如 Goals 部分所述。接下来的两个代码清单演示了 用途@Autowiredon 字段和 setter 方法。应用程序上下文配置 显示在所有示例代码清单之后。spring-doc.cadn.net.cn

以下代码清单中的依赖关系注入行为并非特定于 JUnit 木星。相同的 DI 技术可以与任何支持的测试结合使用 框架。spring-doc.cadn.net.cn

以下示例调用静态断言方法,例如assertNotNull(), 但不在调用前面加上Assertions.在这种情况下,假设 是通过import static未显示在 例。spring-doc.cadn.net.cn

第一个代码清单显示了测试类的基于 JUnit Jupiter 的实现,该 使用@Autowired对于字段注入:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    HibernateTitleRepository titleRepository;

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    lateinit var titleRepository: HibernateTitleRepository

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}

或者,您可以将类配置为使用@Autowired对于 setter 注入,则为 遵循:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    HibernateTitleRepository titleRepository;

    @Autowired
    void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    lateinit var titleRepository: HibernateTitleRepository

    @Autowired
    fun setTitleRepository(titleRepository: HibernateTitleRepository) {
        this.titleRepository = titleRepository
    }

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}

前面的代码清单使用由@ContextConfiguration注解(即repository-config.xml).以下内容 显示了此配置:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>

如果你从 Spring 提供的测试基类扩展,而该基类恰好使用@Autowired在它的一个 setter 方法上,你可能有多个受影响的 bean 类型(例如,多个DataSourcebeans) 的 bean 的 T在 在这种情况下,您可以覆盖 setter 方法并使用@Qualifierannotation 添加到 指示一个特定的目标 Bean,如下所示(但请确保将 delegate 委托给被覆盖的 方法):spring-doc.cadn.net.cn

Java
// ...

    @Autowired
    @Override
    public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

// ...
Kotlin
// ...

    @Autowired
    override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) {
        super.setDataSource(dataSource)
    }

// ...

指定的限定符值表示特定的DataSourcebean 注入, 将类型匹配项集缩小到特定的 bean。其值与<qualifier>声明中的相应<bean>定义。bean 名称 用作回退限定符值,因此您还可以有效地指向特定的 bean 中(如前所示,假设myDataSource是 Beanid).spring-doc.cadn.net.cn

3.5.8. 测试请求和会话范围的 bean

Spring 支持 Request 和 session-scoped bean 的 bean 中,你可以测试你的 request-scoped 和 session-scoped Beans 执行以下步骤:spring-doc.cadn.net.cn

下一个代码片段显示了登录使用案例的 XML 配置。请注意,userServicebean 依赖于请求范围的loginAction豆。此外,LoginAction通过使用 SPEL 表达式进行实例化,这些表达式 从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望 通过 TestContext 框架管理的 mock 配置这些请求参数。 以下清单显示了此使用案例的配置:spring-doc.cadn.net.cn

请求范围的 Bean 配置
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction"/>

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="#{request.getParameter('user')}"
            c:password="#{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy/>
    </bean>

</beans>

RequestScopedBeanTests中,我们将UserService(即 test) 和MockHttpServletRequest添加到我们的测试实例中。在我们的requestScope()test 方法中,我们通过在 提供的MockHttpServletRequest.当loginUser()方法在我们的userService,我们可以确保用户服务可以访问请求范围的loginAction对于当前的MockHttpServletRequest(也就是说,我们只是 set 参数)。然后,我们可以根据已知的 username 和 password 的输入。下面的清单显示了如何做到这一点:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    void requestScope() {
        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();
        // assert results
    }
}
Kotlin
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var request: MockHttpServletRequest

    @Test
    fun requestScope() {
        request.setParameter("user", "enigma")
        request.setParameter("pswd", "\$pr!ng")

        val results = userService.loginUser()
        // assert results
    }
}

下面的代码片段类似于我们之前看到的 request-scoped 豆。然而,这一次,userServicebean 依赖于会话范围的userPreferences豆。请注意,UserPreferencesbean 通过使用 从当前 HTTP 会话中检索主题的 SPEL 表达式。在我们的测试中,我们 需要在 TestContext 框架管理的 mock session 中配置一个主题。这 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

会话范围的 Bean 配置
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences" class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy/>
    </bean>

</beans>

SessionScopedBeanTests,我们将UserServiceMockHttpSession到 我们的测试实例。在我们的sessionScope()test 方法中,我们通过以下方式设置测试夹具 设置预期theme属性MockHttpSession.当processUserPreferences()方法在我们的userService,我们确信 用户服务可以访问会话范围的userPreferences对于当前的MockHttpSession,我们可以根据 配置的主题。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    void sessionScope() throws Exception {
        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();
        // assert results
    }
}
Kotlin
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var session: MockHttpSession

    @Test
    fun sessionScope() {
        session.setAttribute("theme", "blue")

        val results = userService.processUserPreferences()
        // assert results
    }
}

3.5.9. 事务管理

在 TestContext 框架中,事务由TransactionalTestExecutionListener,默认情况下会配置该 显式声明@TestExecutionListeners在你的测试类上。要启用对 transactions 的PlatformTransactionManagerbean 中的ApplicationContext加载了@ContextConfiguration语义(进一步 详细信息将在后面提供)。此外,您必须声明 Spring 的@Transactional注解。spring-doc.cadn.net.cn

测试管理的事务

测试托管事务是使用TransactionalTestExecutionListener或者使用TestTransaction(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆 事务(由 Spring 在ApplicationContext已加载 测试)或应用程序管理的事务(在 由测试调用的应用程序代码)。Spring 管理和应用程序管理 事务通常参与 test-managed 事务。但是,您应该使用 如果 Spring 管理的事务或应用程序管理的事务配置了任何 propagation type (传播类型)REQUIREDSUPPORTS(有关详细信息,请参阅有关事务传播的讨论)。spring-doc.cadn.net.cn

抢占式超时和测试托管事务

在测试框架中使用任何形式的抢占式超时时,必须小心 与 Spring 的 test-managed 事务结合使用。spring-doc.cadn.net.cn

具体来说, Spring 的测试支持将事务状态绑定到当前线程(通过 一个java.lang.ThreadLocal变量)调用。如果 testing 框架在新线程中调用当前测试方法,以支持 preemptive timeout,则在当前测试方法中执行的任何作都不会 在测试托管的事务中调用。因此,任何此类作的结果 不会使用 test-managed 事务回滚。相反,此类作 将提交到持久化存储(例如,关系数据库),甚至 尽管 Spring 正确回滚了 test-managed 事务。spring-doc.cadn.net.cn

可能发生这种情况的情况包括但不限于以下情况。spring-doc.cadn.net.cn

启用和禁用事务

使用@Transactional使测试在 transaction 的事务,默认情况下,在测试完成后自动回滚。 如果测试类带有@Transactional,该类中的每个测试方法 hierarchy 在事务中运行。未使用@Transactional(在类或方法级别)不在事务中运行。注意 那@Transactional在测试生命周期方法上不受支持,例如,方法 用 JUnit Jupiter 的@BeforeAll,@BeforeEach等。此外,测试 的@Transactional但有propagation属性设置为NOT_SUPPORTEDNEVER不在事务中运行。spring-doc.cadn.net.cn

表 1.@Transactional属性支持
属性 支持测试托管事务

valuetransactionManagerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

propagationspring-doc.cadn.net.cn

Propagation.NOT_SUPPORTEDPropagation.NEVER受支持spring-doc.cadn.net.cn

isolationspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

timeoutspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

readOnlyspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

rollbackForrollbackForClassNamespring-doc.cadn.net.cn

否:使用TestTransaction.flagForRollback()相反spring-doc.cadn.net.cn

noRollbackFornoRollbackForClassNamespring-doc.cadn.net.cn

否:使用TestTransaction.flagForCommit()相反spring-doc.cadn.net.cn

方法级生命周期方法 — 例如,使用 JUnit Jupiter 的@BeforeEach@AfterEach— 在测试托管的事务中运行。另一方面 hand、套件级和类级生命周期方法,例如,带有 JUnit Jupiter 的@BeforeAll@AfterAll和用 TestNG 的@BeforeSuite,@AfterSuite,@BeforeClass@AfterClass不在 test-managed 事务。spring-doc.cadn.net.cn

如果您需要在 transaction,您可能希望注入相应的PlatformTransactionManager到 你的 test 类,然后将其与TransactionTemplate对于程序化 事务管理。spring-doc.cadn.net.cn

以下示例演示了为 编写集成测试的常见场景 基于 Hibernate 的UserRepository:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    private int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    private void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    lateinit var repository: HibernateUserRepository

    @Autowired
    lateinit var sessionFactory: SessionFactory

    lateinit var jdbcTemplate: JdbcTemplate

    @Autowired
    fun setDataSource(dataSource: DataSource) {
        this.jdbcTemplate = JdbcTemplate(dataSource)
    }

    @Test
    fun createUser() {
        // track initial state in test database:
        val count = countRowsInTable("user")

        val user = User()
        repository.save(user)

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush()
        assertNumUsers(count + 1)
    }

    private fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    private fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}

Transaction Rollback 和 Commit Behavior 中所述,无需 在createUser()方法运行,因为对 数据库会自动回滚TransactionalTestExecutionListener.spring-doc.cadn.net.cn

事务回滚和提交行为

默认情况下,测试事务将在完成 测试;但是,事务提交和回滚行为可以通过声明方式进行配置 通过@Commit@Rollback附注。有关更多详细信息,请参阅 annotation support 部分中的相应条目。spring-doc.cadn.net.cn

程序化事务管理

您可以使用静态 methods 中的TestTransaction.例如,您可以使用TestTransaction在测试中 methods、before methods和 after 方法来开始或结束当前的测试托管 transaction 或配置当前测试托管的事务以进行 rollback 或 commit。 支持TestTransaction每当TransactionalTestExecutionListener已启用。spring-doc.cadn.net.cn

以下示例演示了TestTransaction.请参阅 javadoc 的TestTransaction了解更多详情。spring-doc.cadn.net.cn

Java
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {

    @Test
    fun transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2)

        deleteFromTables("user")

        // changes to the database will be committed!
        TestTransaction.flagForCommit()
        TestTransaction.end()
        assertFalse(TestTransaction.isActive())
        assertNumUsers(0)

        TestTransaction.start()
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}
在事务外部运行代码

有时,您可能需要在事务测试之前或之后运行某些代码 方法,但在事务上下文之外,例如,要验证初始 数据库状态 行为(如果测试配置为提交事务)。TransactionalTestExecutionListener支持@BeforeTransaction@AfterTransaction注解。您可以注释任何voidmethod 或voiddefault 方法与以下 annotations 和TransactionalTestExecutionListener确保您在 transaction method 或在 transaction method 在适当的时间运行之后。spring-doc.cadn.net.cn

任何 before 方法(例如用 JUnit Jupiter 的@BeforeEach) 和任何 after 方法(例如用 JUnit Jupiter 的@AfterEach) 是 run 在事务中。此外,使用@BeforeTransaction@AfterTransaction不会针对未配置为在 交易。
配置事务管理器

TransactionalTestExecutionListener期望PlatformTransactionManager要成为的 bean 在 Spring 中定义ApplicationContext进行测试。如果有多个实例 之PlatformTransactionManager在测试的ApplicationContext中,您可以声明 qualifier@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer可以由@Configuration类。查阅Javadoc 为TestContextTransactionUtils.retrieveTransactionManager()有关 算法在测试的ApplicationContext.spring-doc.cadn.net.cn

所有与事务相关的 Annotation 的演示

以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试 突出显示所有与事务相关的注释的方案。该示例并非有意为之 演示最佳实践,而是演示这些注释如何 使用。有关详细信息,请参阅注释支持部分 信息和配置示例。事务管理@Sql包含一个额外的示例,该示例使用@Sql为 具有默认事务回滚语义的声明性 SQL 脚本执行。这 以下示例显示了相关的注释:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    void tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    fun verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    fun setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    fun modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    fun tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    fun verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
在测试 ORM 代码时避免误报

当您测试作 Hibernate 会话或 JPA 状态的应用程序代码时 persistence 上下文,请确保刷新测试方法中的底层工作单元 运行该代码。未能刷新基础工作单元可能会产生 false positives:您的测试通过,但相同的代码在实时生产中引发异常 环境。请注意,这适用于维护内存单元的任何 ORM 框架 的工作。在下面基于 Hibernate 的示例测试用例中,一种方法演示了 false positive,而另一种方法正确地公开了刷新 会期:spring-doc.cadn.net.cn

Java
// ...

@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();
}

// ...
Kotlin
// ...

@Autowired
lateinit var sessionFactory: SessionFactory

@Transactional
@Test // no expected exception!
fun falsePositive() {
    updateEntityInHibernateSession()
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
fun updateWithSessionFlush() {
    updateEntityInHibernateSession()
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush()
}

// ...

以下示例显示了 JPA 的匹配方法:spring-doc.cadn.net.cn

Java
// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test // 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)
}

@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Manual flush is required to avoid false positive in test
    entityManager.flush();
}

// ...
Kotlin
// ...

@PersistenceContext
lateinit var entityManager:EntityManager

@Transactional
@Test // no expected exception!
fun falsePositive() {
    updateEntityInJpaPersistenceContext()
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext()
    // Manual flush is required to avoid false positive in test
    entityManager.flush()
}

// ...
测试 ORM 实体生命周期回调

与在测试 ORM 代码时避免误报的注释类似,如果您的应用程序使用实体生命周期回调(还有 称为实体侦听器),请确保刷新 test 中的底层工作单元 运行该代码的方法。未能刷新清除基础工作单元可能会 导致某些生命周期回调未被调用。spring-doc.cadn.net.cn

例如,使用 JPA 时,@PostPersist,@PreUpdate@PostUpdate回调 不会调用entityManager.flush()在实体被 saved 或 updated。同样,如果实体已附加到当前工作单元 (与当前持久化上下文相关联)时,尝试重新加载实体将 不会导致@PostLoadcallback 除非entityManager.clear()在 尝试重新加载实体。spring-doc.cadn.net.cn

以下示例显示如何刷新EntityManager以确保@PostPersist在保留实体时调用回调。具有 一个@PostPersistcallback 方法已注册Person实体 例。spring-doc.cadn.net.cn

Java
// ...

@Autowired
JpaPersonRepository repo;

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test
void savePerson() {
    // EntityManager#persist(...) results in @PrePersist but not @PostPersist
    repo.save(new Person("Jane"));

    // Manual flush is required for @PostPersist callback to be invoked
    entityManager.flush();

    // Test code that relies on the @PostPersist callback
    // having been invoked...
}

// ...
Kotlin
// ...

@Autowired
lateinit var repo: JpaPersonRepository

@PersistenceContext
lateinit var entityManager: EntityManager

@Transactional
@Test
fun savePerson() {
    // EntityManager#persist(...) results in @PrePersist but not @PostPersist
    repo.save(Person("Jane"))

    // Manual flush is required for @PostPersist callback to be invoked
    entityManager.flush()

    // Test code that relies on the @PostPersist callback
    // having been invoked...
}

// ...

有关使用所有 JPA 生命周期回调的工作示例,请参见 Spring Framework 测试套件中的JpaEntityListenerTestsspring-doc.cadn.net.cn

3.5.10. 执行 SQL 脚本

在针对关系数据库编写集成测试时,通常 运行 SQL 脚本以修改数据库架构或将测试数据插入表中。这spring-jdbc模块支持初始化嵌入式或现有数据库 通过在 SpringApplicationContext已加载。请参阅嵌入式数据库支持使用 embedded 数据库spring-doc.cadn.net.cn

尽管初始化数据库以进行测试一次非常有用,但当ApplicationContext加载时,有时必须能够修改 数据库。以下部分介绍如何运行 SQL 在集成测试期间以编程和声明方式编写脚本。spring-doc.cadn.net.cn

以编程方式执行 SQL 脚本

Spring 提供了以下选项,用于在 集成测试方法。spring-doc.cadn.net.cn

ScriptUtils提供用于处理 SQL 的静态实用程序方法的集合 脚本,主要供框架内部使用。但是,如果您 需要完全控制 SQL 脚本的解析和运行方式,ScriptUtils可能适合 您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc methods 中的ScriptUtils了解更多详情。spring-doc.cadn.net.cn

ResourceDatabasePopulator提供基于对象的 API,用于以编程方式填充, 使用外部 资源。ResourceDatabasePopulator提供用于配置字符的选项 encoding、语句分隔符、注释分隔符和错误处理标志 解析并运行脚本。每个配置选项都有一个合理的 default 值。请参阅 javadoc 以获取 有关默认值的详细信息。要运行在ResourceDatabasePopulator中,您可以调用populate(Connection)method 设置为 对java.sql.Connectionexecute(DataSource)方法 要对javax.sql.DataSource.以下示例 为测试架构和测试数据指定 SQL 脚本,将语句分隔符设置为 ,然后针对@@DataSource:spring-doc.cadn.net.cn

Java
@Test
void databaseTest() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // run code that uses the test schema and data
}
Kotlin
@Test
fun databaseTest() {
    val populator = ResourceDatabasePopulator()
    populator.addScripts(
            ClassPathResource("test-schema.sql"),
            ClassPathResource("test-data.sql"))
    populator.setSeparator("@@")
    populator.execute(dataSource)
    // run code that uses the test schema and data
}

请注意,ResourceDatabasePopulator内部委托给ScriptUtils用于解析 以及运行 SQL 脚本。同样,executeSqlScript(..)methods 中的AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests内部使用ResourceDatabasePopulator运行 SQL 脚本。请参阅 Javadoc 以获取 各种executeSqlScript(..)方法了解更多详细信息。spring-doc.cadn.net.cn

使用 @Sql 以声明方式执行 SQL 脚本

除了上述以编程方式运行 SQL 脚本的机制外, 你可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。 具体来说,您可以声明@Sql注解添加到 配置单个 SQL 语句或 SQL 脚本的资源路径,这些脚本应该是 在集成测试方法之前或之后针对给定数据库运行。支持@SqlSqlScriptsTestExecutionListener,默认情况下处于启用状态。spring-doc.cadn.net.cn

方法级@Sql默认情况下,声明会覆盖类级声明。如 但是,此行为可以按测试类或 测试方法 VIA@SqlMergeMode.看合并和覆盖配置@SqlMergeMode了解更多详情。
路径资源语义

每个路径都解释为一个 SpringResource.普通路径(例如"schema.sql") 被视为相对于 定义测试类。以斜杠开头的路径被视为绝对路径 Classpath 资源(例如"/org/example/schema.sql").引用 URL(例如,前缀为classpath:,file:,http:) 使用 指定的资源协议。spring-doc.cadn.net.cn

以下示例演示如何使用@Sql在类级别和方法级别 在基于 JUnit Jupiter 的集成测试类中:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    void emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest() {
        // run code that uses the test schema and test data
    }
}
Kotlin
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    fun emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql("/test-schema.sql", "/test-user-data.sql")
    fun userTest() {
        // run code that uses the test schema and test data
    }
}
默认脚本检测

如果未指定 SQL 脚本或语句,则尝试检测defaultscript 的@Sql已声明。如果无法检测到默认值,则IllegalStateException被抛出。spring-doc.cadn.net.cn

  • 类级声明:如果带注释的测试类为com.example.MyTest这 对应的默认脚本是classpath:com/example/MyTest.sql.spring-doc.cadn.net.cn

  • 方法级声明:如果带注释的测试方法命名为testMethod()和 is 在类中定义com.example.MyTest,对应的默认脚本为classpath:com/example/MyTest.testMethod.sql.spring-doc.cadn.net.cn

声明多个@Sql

如果需要为给定的测试类或测试配置多组 SQL 脚本 方法,但具有不同的语法配置、不同的错误处理规则,或者 每个 set 的执行阶段不同,你可以声明@Sql.跟 Java 8 中,您可以使用@Sql作为可重复的注释。否则,您可以使用@SqlGroup注解作为显式容器来声明@Sql.spring-doc.cadn.net.cn

以下示例演示如何使用@Sql作为 Java 8 的可重复注释:spring-doc.cadn.net.cn

Java
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
    // run code that uses the test schema and test data
}
Kotlin
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin

在前面示例所示的场景中,test-schema.sqlscript 使用 单行注释的不同语法。spring-doc.cadn.net.cn

以下示例与前面的示例相同,只是@Sql声明在@SqlGroup.在 Java 8 及更高版本中,使用@SqlGroup是可选的,但您可能需要使用@SqlGroup为了兼容 其他 JVM 语言,例如 Kotlin。spring-doc.cadn.net.cn

Java
@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
Kotlin
@Test
@SqlGroup(
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // Run code that uses the test schema and test data
}
脚本执行阶段

默认情况下,SQL 脚本在相应的测试方法之前运行。但是,如果 您需要在测试方法之后运行一组特定的脚本(例如,要清理 up 数据库状态),您可以使用executionPhase属性@Sql,作为 以下示例显示:spring-doc.cadn.net.cn

Java
@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
void userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}
Kotlin
@Test
@SqlGroup(
    Sql("create-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED)),
    Sql("delete-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED),
        executionPhase = AFTER_TEST_METHOD))
fun userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}

请注意,ISOLATEDAFTER_TEST_METHOD静态导入自Sql.TransactionModeSql.ExecutionPhase分别。spring-doc.cadn.net.cn

Script Configuration 替换为@SqlConfig

您可以使用@SqlConfig注解。 当在集成测试类上声明为类级注释时,@SqlConfig用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候 使用config属性的@Sql注解@SqlConfig用作在封闭中声明的 SQL 脚本的本地配置@Sql注解。中的每个属性@SqlConfig具有隐式默认值,即 记录在相应属性的 javadoc 中。由于为 annotation 属性,遗憾的是,它不是 可以分配null添加到 annotation 属性中。因此,为了 支持继承的全局配置的覆盖,@SqlConfigattributes 具有 显式默认值 (for Strings)、(for arrays) 或""{}DEFAULT(对于 枚举)。这种方法允许@SqlConfig选择性覆盖 全局声明中的各个属性@SqlConfig通过提供值 Other than 、 或""{}DEFAULT.全球@SqlConfig每当 当地@SqlConfigattributes 不提供除 、 、 或""{}DEFAULT.因此,显式本地配置将覆盖全局配置。spring-doc.cadn.net.cn

提供的配置选项@Sql@SqlConfig等同于那些 支持单位ScriptUtilsResourceDatabasePopulator而是那些 由<jdbc:initialize-database/>XML 命名空间元素。请参阅 javadoc 中的各个属性@Sql@SqlConfig了解详情。spring-doc.cadn.net.cn

事务管理@Sqlspring-doc.cadn.net.cn

默认情况下,SqlScriptsTestExecutionListener推断所需的交易 使用@Sql.具体来说,SQL 脚本是运行的 如果没有事务,则在现有的 Spring 管理的事务中(例如, 事务由TransactionalTestExecutionListener对于带有@Transactional)或在隔离的事务中,具体取决于配置的值 的transactionMode属性@SqlConfig以及PlatformTransactionManager在测试的ApplicationContext.作为最低限度, 但是,javax.sql.DataSource必须存在于测试的ApplicationContext.spring-doc.cadn.net.cn

如果SqlScriptsTestExecutionListener要检测DataSourcePlatformTransactionManager并推断事务语义不适合您的需求, 您可以通过设置dataSourcetransactionManager的属性@SqlConfig.此外,您可以控制事务传播 行为,方法是将transactionMode属性@SqlConfig(例如,如果 脚本应在隔离的事务中运行)。虽然对所有 事务管理支持的选项@Sql超出了此范围 参考手册,用于@SqlConfigSqlScriptsTestExecutionListener提供详细信息,以下示例显示了典型的测试场景 它使用 JUnit Jupiter 和事务测试@Sql:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

    final JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // run code that uses the test data...
    }

    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}
Kotlin
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {

    val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)

    @Test
    @Sql("/test-data.sql")
    fun usersTest() {
        // verify state in test database:
        assertNumUsers(2)
        // run code that uses the test data...
    }

    fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    fun assertNumUsers(expected: Int) {
        assertEquals(expected, countRowsInTable("user"),
                "Number of rows in the [user] table.")
    }
}

请注意,在usersTest()method 为 run,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sql脚本)会自动回滚TransactionalTestExecutionListener(请参阅 事务管理 详细信息)。spring-doc.cadn.net.cn

合并和覆盖配置@SqlMergeMode

从 Spring Framework 5.2 开始,可以合并方法级@Sql声明 类级声明。例如,这允许您为 数据库架构或一些常见的测试数据,然后提供额外的 每个测试方法的用例特定测试数据。要启用@Sqlmergeging 中,注释 您的 test 类或 test method 替换为@SqlMergeMode(MERGE).要禁用 Specific Test Method(或Specific Test Subclass),可以切换回默认模式 通过@SqlMergeMode(OVERRIDE).查阅@SqlMergeMode注释文档部分以获取示例和更多详细信息。spring-doc.cadn.net.cn

3.5.11. 并行测试执行

Spring Framework 5.0 引入了对在 单个 JVM。一般来说,这意味着大多数 测试类或测试方法可以并行运行,而无需对测试代码进行任何更改 或 configuration。spring-doc.cadn.net.cn

有关如何设置并行测试执行的详细信息,请参阅 测试框架、构建工具或 IDE。

请记住,将并发引入测试套件可能会导致 意外的副作用、奇怪的运行时行为以及间歇性失败的测试,或者 似乎是随机的。因此,Spring Team 提供了以下一般准则 for when 不并行运行测试。spring-doc.cadn.net.cn

如果测试符合以下条件,则不要并行运行测试:spring-doc.cadn.net.cn

  • 使用 Spring Framework 的@DirtiesContext支持。spring-doc.cadn.net.cn

  • 使用 Spring Boot 的@MockBean@SpyBean支持。spring-doc.cadn.net.cn

  • 使用 JUnit 4 的@FixMethodOrder支持或任何测试框架功能 旨在确保测试方法按特定顺序运行。注意 但是,如果整个测试类并行运行,则这不适用。spring-doc.cadn.net.cn

  • 更改共享服务或系统的状态,例如数据库、消息代理、 filesystem 等。这适用于嵌入式系统和外部系统。spring-doc.cadn.net.cn

如果并行测试执行失败并出现异常,指出ApplicationContext对于当前测试不再处于活动状态,这通常意味着ApplicationContext已从ContextCache在不同的线程中。spring-doc.cadn.net.cn

这可能是由于使用了@DirtiesContext或由于从ContextCache.如果@DirtiesContext是罪魁祸首,你要么需要想办法 避免使用@DirtiesContext或从并行执行中排除此类测试。如果 最大大小ContextCache已超出,您可以增加最大大小 缓存中。有关详细信息,请参阅 context caching 上的讨论。spring-doc.cadn.net.cn

只有在以下情况下,才能在 Spring TestContext 框架中并行执行测试 标的TestContextimplementation 提供了一个 copy 构造函数,如 用于TestContext.这DefaultTestContextused 提供了这样的构造函数。但是,如果您使用 提供自定义TestContextimplementation 中,您需要 验证它是否适合并行测试执行。

3.5.12. TestContext 框架支持类

本节描述了支持 Spring TestContext 框架的各种类。spring-doc.cadn.net.cn

Spring JUnit 4 运行程序

Spring TestContext 框架通过自定义 runner(在 JUnit 4.12 或更高版本上受支持)。通过使用@RunWith(SpringJUnit4ClassRunner.class)或较短的@RunWith(SpringRunner.class)变体,开发人员可以实现基于 JUnit 4 的标准单元和集成测试,以及 同时获得 TestContext 框架的好处,例如支持 加载应用程序上下文, 测试实例的依赖注入, 事务测试 method 执行,依此类推。如果你想将 Spring TestContext 框架与 替代运行程序(例如 JUnit 4 的Parameterizedrunner) 或第三方 runner (例如MockitoJUnitRunner),您可以选择改用 Spring 对 JUnit 规则的支持spring-doc.cadn.net.cn

以下代码清单显示了将测试类配置为 使用自定义 Spring 运行Runner:spring-doc.cadn.net.cn

Java
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // test logic...
    }
}
Kotlin
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {

    @Test
    fun testMethod() {
        // test logic...
    }
}

在前面的示例中,@TestExecutionListeners配置了空列表,以 禁用默认侦听器,否则需要ApplicationContext自 通过@ContextConfiguration.spring-doc.cadn.net.cn

Spring JUnit 4 规则

org.springframework.test.context.junit4.rulespackage 提供以下 JUnit 4 个规则(在 JUnit 4.12 或更高版本上受支持):spring-doc.cadn.net.cn

SpringClassRule是一个 JUnitTestRule支持 Spring 的类级功能 TestContext Framework 的 Framework,而SpringMethodRule是一个 JUnitMethodRule那个辅助 Spring TestContext 框架的实例级和方法级功能。spring-doc.cadn.net.cn

SpringRunner,Spring 基于规则的 JUnit 支持具有以下优点 独立于任何org.junit.runner.Runnerimplementation 的 ,因此可以是 与现有的替代运行程序(如 JUnit 4 的Parameterized) 或 第三方运行程序(例如MockitoJUnitRunner).spring-doc.cadn.net.cn

为了支持 TestContext 框架的全部功能,您必须将SpringClassRule替换为SpringMethodRule.以下示例显示了正确的方法 要在集成测试中声明这些规则:spring-doc.cadn.net.cn

Java
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void testMethod() {
        // test logic...
    }
}
Kotlin
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {

    @Rule
    val springMethodRule = SpringMethodRule()

    @Test
    fun testMethod() {
        // test logic...
    }

    companion object {
        @ClassRule
        val springClassRule = SpringClassRule()
    }
}
JUnit 4 支持类

org.springframework.test.context.junit4package 提供以下支持 类(在 JUnit 4.12 或更高版本上受支持):spring-doc.cadn.net.cn

AbstractJUnit4SpringContextTests是一个抽象基测试类,它集成了 具有显式ApplicationContexttesting 支持在 JUnit 4 环境中。当您扩展AbstractJUnit4SpringContextTests中,您可以访问protected applicationContext实例变量,可用于执行显式 bean 查找或测试整个上下文的状态。spring-doc.cadn.net.cn

AbstractTransactionalJUnit4SpringContextTestsAbstractJUnit4SpringContextTests为 JDBC 添加了一些方便的功能 访问。此类需要javax.sql.DataSourcebean 和PlatformTransactionManagerbean 在ApplicationContext.当您 扩展AbstractTransactionalJUnit4SpringContextTests中,您可以访问protected jdbcTemplateinstance 变量,可用于运行 SQL 语句以查询 数据库。您可以使用此类查询来确认之前和之后的数据库状态 运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在 与应用程序代码相同的事务的范围。当与 一个 ORM 工具,一定要避免误报。 如 JDBC 测试支持中所述,AbstractTransactionalJUnit4SpringContextTests还提供了方便的方法,这些方法 委托给JdbcTestUtils通过使用上述jdbcTemplate. 此外AbstractTransactionalJUnit4SpringContextTests提供了一个executeSqlScript(..)针对配置的DataSource.spring-doc.cadn.net.cn

这些类便于扩展。如果您不需要您的测试类 要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试 类@RunWith(SpringRunner.class)Spring 的 JUnit 规则
用于 JUnit Jupiter 的 SpringExtension

Spring TestContext 框架提供了与 JUnit Jupiter 测试的完全集成 框架,在 JUnit 5 中引入。通过使用@ExtendWith(SpringExtension.class)中,您可以实现基于 JUnit Jupiter 的标准单元 和集成测试,同时获得 TestContext 框架的好处, 例如支持加载应用程序上下文、测试实例的依赖注入、 事务性测试方法执行,等等。spring-doc.cadn.net.cn

此外,由于 JUnit Jupiter 中丰富的扩展 API,Spring 提供了 Spring 支持 JUnit 4 和 TestNG 的:spring-doc.cadn.net.cn

以下代码清单显示了如何配置测试类以使用SpringExtension@ContextConfiguration:spring-doc.cadn.net.cn

Java
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension::class)
// Instructs Spring to load an ApplicationContext from TestConfig::class
@ContextConfiguration(classes = [TestConfig::class])
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

由于你也可以使用 JUnit 5 中的注释作为元注释,因此 Spring 提供了@SpringJUnitConfig@SpringJUnitWebConfig编写的注释来简化 测试的配置ApplicationContext和 JUnit Jupiter。spring-doc.cadn.net.cn

以下示例使用@SpringJUnitConfig减少配置量 在前面的例子中使用:spring-doc.cadn.net.cn

Java
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig::class)
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

同样,以下示例使用@SpringJUnitWebConfig要创建一个WebApplicationContext用于 JUnit Jupiter:spring-doc.cadn.net.cn

Java
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig::class
@SpringJUnitWebConfig(TestWebConfig::class)
class SimpleWebTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

请参阅以下文档@SpringJUnitConfig@SpringJUnitWebConfigSpring JUnit Jupiter Testing Annotations 中了解更多详细信息。spring-doc.cadn.net.cn

依赖项注入SpringExtension

SpringExtension实现ParameterResolver来自 JUnit Jupiter 的扩展 API,它允许 Spring 为测试提供依赖注入 构造函数、测试方法和测试生命周期回调方法。spring-doc.cadn.net.cn

具体说来SpringExtension可以从测试的ApplicationContext转换为带有@BeforeAll,@AfterAll,@BeforeEach,@AfterEach,@Test,@RepeatedTest,@ParameterizedTest等。spring-doc.cadn.net.cn

构造函数注入

如果 JUnit Jupiter 测试类的构造函数中的特定参数的类型为ApplicationContext(或其子类型)或带有@Autowired,@Qualifier@Value,Spring 会注入该特定 参数替换为相应的 bean 或来自测试的ApplicationContext.spring-doc.cadn.net.cn

如果 Spring 如果 构造函数被认为是可自动布线的。构造函数被视为 如果满足以下条件之一,则为 autowirable(按优先顺序)。spring-doc.cadn.net.cn

@TestConstructor有关使用@TestConstructor以及如何更改 Global Test Constructor autowire 模式spring-doc.cadn.net.cn

如果测试类的构造函数被认为是可自动布线的,则 Spring 承担解析构造函数中所有参数的参数的责任。 因此,没有其他ParameterResolver注册到 JUnit Jupiter 可以解析 参数。

测试类的构造函数注入不能与 JUnit 结合使用 木星的@TestInstance(PER_CLASS)支持 if@DirtiesContext用于关闭 test 的ApplicationContext测试方法之前或之后。spring-doc.cadn.net.cn

原因是@TestInstance(PER_CLASS)指示 JUnit Jupiter 缓存测试 实例。因此,测试实例将保留 对最初从ApplicationContext该 随后关闭。由于测试类的构造函数只会被调用 一旦在这种情况下,依赖注入将不会再次发生,后续测试 将与来自已关闭的 bean 交互ApplicationContext这可能会导致错误。spring-doc.cadn.net.cn

要使用@DirtiesContext在 “before test method” 或 “after test method” 模式下 结合@TestInstance(PER_CLASS),必须从 Spring 配置依赖项 通过 field 或 setter 注入提供,以便它们可以在测试之间重新注入 方法调用。spring-doc.cadn.net.cn

在以下示例中, Spring 将OrderServicebean 的ApplicationContext加载自TestConfig.classOrderServiceIntegrationTests构造 函数。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    @Autowired
    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
    // tests that use the injected OrderService
}

请注意,此功能允许测试依赖项final因此是不可变的。spring-doc.cadn.net.cn

如果spring.test.constructor.autowire.modeproperty 是all(参见@TestConstructor),我们可以省略@Autowired在上一个示例中的构造函数上,从而产生以下内容。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
    // tests that use the injected OrderService
}
方法注入

如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数为 类型ApplicationContext(或其子类型)或带有@Autowired,@Qualifier@Value,Spring 会注入该特定 参数替换为测试的ApplicationContext.spring-doc.cadn.net.cn

在以下示例中, Spring 将OrderServiceApplicationContext加载自TestConfig.classdeleteOrder()测试方法:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @Test
    void deleteOrder(@Autowired OrderService orderService) {
        // use orderService from the test's ApplicationContext
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @Test
    fun deleteOrder(@Autowired orderService: OrderService) {
        // use orderService from the test's ApplicationContext
    }
}

由于ParameterResolver支持,您还可以 将多个依赖项注入到单个方法中,不仅来自 Spring,而且来自 从 JUnit Jupiter 本身或其他第三方扩展。spring-doc.cadn.net.cn

以下示例显示了如何同时让 Spring 和 JUnit Jupiter 注入依赖项 到placeOrderRepeatedly()测试方法。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
            @Autowired OrderService orderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}

请注意,使用@RepeatedTest来自 JUnit Jupiter 允许测试方法获得访问权限 到RepetitionInfo.spring-doc.cadn.net.cn

@Nestedtest 类配置

Spring TestContext 框架支持在@Nested自 Spring Framework 5.0 以来,JUnit Jupiter 中的 test 类;然而,直到Spring 框架 5.3 类级测试配置注释不是 像来自超类一样封闭类。spring-doc.cadn.net.cn

Spring Framework 5.3 引入了对继承测试类的一流支持 configuration 的 set,并且此类配置将由 违约。从默认值更改INHERITmode 设置为OVERRIDE模式中,您可以注释 个人@Nestedtest 类替换为@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE).显式的@NestedTestConfiguration声明将应用于带注释的测试类以及 它的任何子类和嵌套类。因此,您可以注释顶级测试类 跟@NestedTestConfiguration,这将应用于其所有嵌套测试类 递 归。spring-doc.cadn.net.cn

为了允许开发团队将默认值更改为OVERRIDE–例如 为了与 Spring Framework 5.0 到 5.2 兼容 – 可以更改默认模式 通过 JVM 系统属性或spring.properties文件位于 classpath 的请参阅“更改 默认的 Encovering Configuration Inheritance Mode“注释。spring-doc.cadn.net.cn

尽管下面的 “Hello World” 示例非常简单,但它展示了如何声明 通用配置,该配置由@Nested测试 类。在这个特定示例中,只有TestConfigConfiguration 类为 继承。每个嵌套测试类都提供自己的一组活动配置文件,从而生成一个 不同ApplicationContext对于每个嵌套的测试类(有关详细信息,请参阅 Context Caching)。请参阅支持的注释列表以查看 哪些注解可以在@Nestedtest 类。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class GreetingServiceTests {

    @Nested
    @ActiveProfiles("lang_en")
    class EnglishGreetings {

        @Test
        void hello(@Autowired GreetingService service) {
            assertThat(service.greetWorld()).isEqualTo("Hello World");
        }
    }

    @Nested
    @ActiveProfiles("lang_de")
    class GermanGreetings {

        @Test
        void hello(@Autowired GreetingService service) {
            assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
        }
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class GreetingServiceTests {

    @Nested
    @ActiveProfiles("lang_en")
    inner class EnglishGreetings {

        @Test
        fun hello(@Autowired service:GreetingService) {
            assertThat(service.greetWorld()).isEqualTo("Hello World")
        }
    }

    @Nested
    @ActiveProfiles("lang_de")
    inner class GermanGreetings {

        @Test
        fun hello(@Autowired service:GreetingService) {
            assertThat(service.greetWorld()).isEqualTo("Hallo Welt")
        }
    }
}
TestNG 支持类

org.springframework.test.context.testngpackage 提供以下支持 类:spring-doc.cadn.net.cn

AbstractTestNGSpringContextTests是一个抽象基测试类,它集成了 具有显式ApplicationContexttesting 支持在 TestNG 环境。当您扩展AbstractTestNGSpringContextTests中,您可以访问protected applicationContext实例变量,可用于执行显式 bean 查找或测试整个上下文的状态。spring-doc.cadn.net.cn

AbstractTransactionalTestNGSpringContextTestsAbstractTestNGSpringContextTests为 JDBC 添加了一些方便的功能 访问。此类需要javax.sql.DataSourcebean 和PlatformTransactionManagerbean 在ApplicationContext.当您 扩展AbstractTransactionalTestNGSpringContextTests中,您可以访问protected jdbcTemplateinstance 变量,可用于运行 SQL 语句以查询 数据库。您可以使用此类查询来确认之前和之后的数据库状态 运行与数据库相关的应用程序代码,并且 Spring 确保此类查询在 与应用程序代码相同的事务的范围。当与 一个 ORM 工具,一定要避免误报。 如 JDBC 测试支持中所述,AbstractTransactionalTestNGSpringContextTests还提供了方便的方法,这些方法 委托给JdbcTestUtils通过使用上述jdbcTemplate. 此外AbstractTransactionalTestNGSpringContextTests提供了一个executeSqlScript(..)针对配置的DataSource.spring-doc.cadn.net.cn

这些类便于扩展。如果您不需要您的测试类 要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试 类@ContextConfiguration,@TestExecutionListeners等 和 by 使用TestContextManager.查看源代码 之AbstractTestNGSpringContextTests,了解如何检测测试类的示例。

3.6. WebTestClient 客户端

WebTestClient是专为测试服务器应用程序而设计的 HTTP 客户端。它包装 Spring 的 WebClient 并使用它来执行请求 但公开了一个用于验证响应的 testing 门面。WebTestClient可用于 执行端到端 HTTP 测试。它还可用于测试 Spring MVC 和 Spring WebFlux 没有正在运行的服务器的应用程序通过 Mock Server 请求和响应对象。spring-doc.cadn.net.cn

Kotlin 用户:请参阅此部分,了解如何使用WebTestClient.

3.6.1. 设置

要设置WebTestClient您需要选择要绑定到的服务器设置。这可以是 的几个模拟服务器设置选项或连接到实时服务器。spring-doc.cadn.net.cn

绑定到控制器

此设置允许您通过 mock 请求和响应对象测试特定的控制器, 没有正在运行的服务器。spring-doc.cadn.net.cn

对于 WebFlux 应用程序,使用以下命令加载相当于 WebFlux Java 配置的基础设施,注册给定的 控制器,并创建一个 WebHandler 链来处理请求:spring-doc.cadn.net.cn

Java
WebTestClient client =
        WebTestClient.bindToController(new TestController()).build();
Kotlin
val client = WebTestClient.bindToController(TestController()).build()

对于 Spring MVC,请使用以下委托给 StandaloneMockMvcBuilder 来加载等效于 WebMvc Java 配置的基础设施。 注册给定的控制器,并创建一个 MockMvc 实例来处理请求:spring-doc.cadn.net.cn

Java
WebTestClient client =
        MockMvcWebTestClient.bindToController(new TestController()).build();
Kotlin
val client = MockMvcWebTestClient.bindToController(TestController()).build()
绑定到ApplicationContext

此设置允许您使用 Spring MVC 或 Spring WebFlux 加载 Spring 配置 infrastructure 和 controller 声明,并使用它通过 mock 请求处理请求 和 response 对象,但没有正在运行的服务器。spring-doc.cadn.net.cn

对于 WebFlux,请使用以下命令,其中 SpringApplicationContext传递给 WebHttpHandlerBuilder 以创建 WebHandler 链来处理 请求:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {

    WebTestClient client;

    @BeforeEach
    void setUp(ApplicationContext context) {  (2)
        client = WebTestClient.bindToApplicationContext(context).build(); (3)
    }
}
1 指定要加载的配置
2 注入配置
3 创建WebTestClient
Kotlin
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {

    lateinit var client: WebTestClient

    @BeforeEach
    fun setUp(context: ApplicationContext) { (2)
        client = WebTestClient.bindToApplicationContext(context).build() (3)
    }
}
1 指定要加载的配置
2 注入配置
3 创建WebTestClient

对于 Spring MVC,请使用以下命令,其中 SpringApplicationContext传递给 MockMvcBuilders.webAppContextSetup 以创建一个 MockMvc 实例来处理 请求:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
    @ContextConfiguration(classes = RootConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class MyTests {

    @Autowired
    WebApplicationContext wac; (2)

    WebTestClient client;

    @BeforeEach
    void setUp() {
        client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); (3)
    }
}
1 指定要加载的配置
2 注入配置
3 创建WebTestClient
Kotlin
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
    @ContextConfiguration(classes = RootConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class MyTests {

    @Autowired
    lateinit var wac: WebApplicationContext; (2)

    lateinit var client: WebTestClient

    @BeforeEach
    fun setUp() { (2)
        client = MockMvcWebTestClient.bindToApplicationContext(wac).build() (3)
    }
}
1 指定要加载的配置
2 注入配置
3 创建WebTestClient
绑定到路由器功能

此设置允许您通过以下方式测试功能端点 mock 请求和响应对象,而无需正在运行的服务器。spring-doc.cadn.net.cn

对于 WebFlux,请使用以下命令,将RouterFunctions.toWebHandler自 创建 Server Setup 以处理请求:spring-doc.cadn.net.cn

Java
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
Kotlin
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()

对于 Spring MVC,目前没有测试 WebMvc 功能端点的选项。spring-doc.cadn.net.cn

绑定到服务器

此设置连接到正在运行的服务器以执行完整的端到端 HTTP 测试:spring-doc.cadn.net.cn

Java
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
Kotlin
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
客户端配置

除了前面描述的 server 设置选项之外,您还可以配置 client 选项,包括基本 URL、默认标头、客户端筛选器等。这些选项 在以下位置随时可用bindToServer().对于所有其他配置选项, 您需要使用configureClient()要从 Server 过渡到 Client 端配置,请作为 遵循:spring-doc.cadn.net.cn

Java
client = WebTestClient.bindToController(new TestController())
        .configureClient()
        .baseUrl("/test")
        .build();
Kotlin
client = WebTestClient.bindToController(TestController())
        .configureClient()
        .baseUrl("/test")
        .build()

3.6.2. 编写测试

WebTestClient提供与 WebClient 相同的 API,直到使用exchange().有关如何执行以下作的示例,请参阅 WebClient 文档 准备包含任何内容(包括表单数据、多部分数据等)的请求。spring-doc.cadn.net.cn

调用exchange(),WebTestClientWebClient和 而是继续使用工作流来验证响应。spring-doc.cadn.net.cn

要断言响应状态和标头,请使用以下内容:spring-doc.cadn.net.cn

Java
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON);
Kotlin
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON)

如果您希望即使其中一个期望失败,也可以断言所有期望,则可以 用expectAll(..)而不是多个链接expect*(..)调用。此功能是 类似于 AssertJ assertAll()支持 JUnit Jupiter.spring-doc.cadn.net.cn

Java
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectAll(
        spec -> spec.expectStatus().isOk(),
        spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
    );

然后,您可以选择通过以下方法之一对响应正文进行解码:spring-doc.cadn.net.cn

并对生成的更高级别 Object 执行断言:spring-doc.cadn.net.cn

Java
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList(Person.class).hasSize(3).contains(person);
Kotlin
import org.springframework.test.web.reactive.server.expectBodyList

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList<Person>().hasSize(3).contains(person)

如果内置断言不足,您可以改用该对象 和 执行任何其他断言:spring-doc.cadn.net.cn

Java
import org.springframework.test.web.reactive.server.expectBody

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .consumeWith(result -> {
            // custom assertions (e.g. AssertJ)...
        });
Kotlin
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody<Person>()
        .consumeWith {
            // custom assertions (e.g. AssertJ)...
        }

或者,您可以退出工作流程并获取EntityExchangeResult:spring-doc.cadn.net.cn

Java
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
Kotlin
import org.springframework.test.web.reactive.server.expectBody

val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk
        .expectBody<Person>()
        .returnResult()
当您需要使用泛型解码为目标类型时,请查找重载的方法 接受ParameterizedTypeReference而不是Class<T>.
无内容

如果响应不需要包含内容,则可以按如下方式断言:spring-doc.cadn.net.cn

Java
client.post().uri("/persons")
        .body(personMono, Person.class)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty();
Kotlin
client.post().uri("/persons")
        .bodyValue(person)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty()

如果要忽略响应内容,以下将释放内容,而不会 任何断言:spring-doc.cadn.net.cn

Java
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound()
        .expectBody(Void.class);
Kotlin
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound
        .expectBody<Unit>()
JSON 内容

您可以使用expectBody()没有目标类型对原始 content 而不是通过更高级别的 Object(s)。spring-doc.cadn.net.cn

要使用 JSONAssert 验证完整的 JSON 内容:spring-doc.cadn.net.cn

Java
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")
Kotlin
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")

要使用 JSONPath 验证 JSON 内容:spring-doc.cadn.net.cn

Java
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason");
Kotlin
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason")
流式响应

要测试潜在的无限流(例如"text/event-stream""application/x-ndjson",首先验证响应状态和标头,然后 获取FluxExchangeResult:spring-doc.cadn.net.cn

Java
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult(MyEvent.class);
Kotlin
import org.springframework.test.web.reactive.server.returnResult

val result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult<MyEvent>()

现在,您已准备好使用StepVerifierreactor-test:spring-doc.cadn.net.cn

Java
Flux<Event> eventFlux = result.getResponseBody();

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith(p -> ...)
        .thenCancel()
        .verify();
Kotlin
val eventFlux = result.getResponseBody()

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith { p -> ... }
        .thenCancel()
        .verify()
MockMvc 断言

WebTestClient是 HTTP 客户端,因此它只能验证客户端中的内容 响应,包括 status、Headers 和 Body。spring-doc.cadn.net.cn

当使用 MockMvc 服务器设置测试 Spring MVC 应用程序时,您有额外的 选择对服务器响应执行进一步的断言。为此,首先 获取ExchangeResult在断言 body 之后:spring-doc.cadn.net.cn

Java
// For a response with a body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();
Kotlin
// For a response with a body
val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
val result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();

然后切换到 MockMvc 服务器响应断言:spring-doc.cadn.net.cn

Java
MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));
Kotlin
MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));

3.7. MockMvc 函数

Spring MVC 测试框架(也称为 MockMvc)为测试 Spring 提供支持 MVC 应用程序。它执行完整的 Spring MVC 请求处理,但通过模拟请求和 response 对象,而不是正在运行的服务器。spring-doc.cadn.net.cn

MockMvc 可以单独用于执行请求和验证响应。它也可以是 通过 WebTestClient 使用,其中 MockMvc 作为服务器插入来处理 请求与。优势WebTestClient是使用更高级别的选项 对象而不是原始数据,以及切换到完整的端到端 HTTP 的能力 测试并使用相同的测试 API。spring-doc.cadn.net.cn

3.7.1. 概述

您可以通过实例化控制器、注入控制器来为 Spring MVC 编写简单的单元测试 替换为依赖项,并调用其方法。但是,此类测试不会验证请求 映射、数据绑定、消息转换、类型转换、验证和 NOR 他们是否涉及任何支持@InitBinder,@ModelAttribute@ExceptionHandler方法。spring-doc.cadn.net.cn

Spring MVC 测试框架,也称为MockMvc,旨在提供更完整的 在没有运行服务器的情况下测试 Spring MVC 控制器。它通过调用 这DispatcherServlet并从spring-test模块复制完整的 Spring MVC 请求处理,而无需 正在运行的服务器。spring-doc.cadn.net.cn

MockMvc 是一个服务器端测试框架,可让您验证大部分功能 使用轻量级和目标测试的 Spring MVC 应用程序。您可以在 它自己的 API 来执行请求和验证响应,或者您也可以通过 WebTestClient API,其中插入了 MockMvc 作为服务器来处理请求 跟。spring-doc.cadn.net.cn

静态导入

当直接使用 MockMvc 执行请求时,您需要静态导入:spring-doc.cadn.net.cn

记住这一点的一种简单方法是搜索MockMvc*.如果使用 Eclipse,请确保同时 将上述内容添加为 Eclipse 首选项中的 “favorite static members”。spring-doc.cadn.net.cn

通过 WebTestClient 使用 MockMvc 时,您不需要静态导入。 这WebTestClient提供没有静态导入的 Fluent API。spring-doc.cadn.net.cn

设置选项

可以通过以下两种方式之一设置 MockMvc。一种是直接指向 想要测试和以编程方式配置 Spring MVC 基础设施。第二个是 指向其中包含 Spring MVC 和控制器基础结构的 Spring 配置。spring-doc.cadn.net.cn

要设置 MockMvc 以测试特定控制器,请使用以下内容:spring-doc.cadn.net.cn

Java
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

}
Kotlin
class MyWebTests {

    lateinit var mockMvc : MockMvc

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }

    // ...

}

或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该客户端委托给同一个构建器 如上所示。spring-doc.cadn.net.cn

要通过 Spring 配置设置 MockMvc,请使用以下内容:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {

    lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该客户端委托给同一个构建器 如上所示。spring-doc.cadn.net.cn

您应该使用哪个设置选项?spring-doc.cadn.net.cn

webAppContextSetup加载实际的 Spring MVC 配置,从而产生一个 完成集成测试。由于 TestContext 框架缓存了加载的 Spring 配置中,它有助于保持测试快速运行,即使您在 测试套件。此外,你可以通过 Spring 将 mock 服务注入控制器 配置以继续专注于测试 Web 层。以下示例声明 带有 Mockito 的 mock 服务:spring-doc.cadn.net.cn

<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

然后,您可以将 mock 服务注入到测试中,以设置和验证您的 期望,如下例所示:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {

    @Autowired
    AccountService accountService;

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {

    @Autowired
    lateinit var accountService: AccountService

    lateinit mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

standaloneSetup,则更接近于单元测试。它测试一个 controller 的 Controller。你可以手动注入控制器的 mock 依赖项,并且 它不涉及加载 Spring 配置。此类测试更侧重于风格 并更容易查看正在测试的控制器,是否有任何特定的 Spring 需要 MVC 配置才能工作,依此类推。这standaloneSetup也是一个非常 编写临时测试以验证特定行为或调试问题的便捷方法。spring-doc.cadn.net.cn

与大多数“集成与单元测试”的争论一样,没有对错之分 答。但是,使用standaloneSetup确实意味着需要额外的webAppContextSetup测试来验证你的 Spring MVC 配置。 或者,您可以使用webAppContextSetup,以便始终 针对你的实际 Spring MVC 配置进行测试。spring-doc.cadn.net.cn

设置功能

无论您使用哪个 MockMvc 构建器,所有MockMvcBuilderimplementations 提供 一些常见且非常有用的功能。例如,您可以声明Accept标头 all 请求,并期望状态为 200 以及Content-Type标头 响应,如下所示:spring-doc.cadn.net.cn

Java
// static import of MockMvcBuilders.standaloneSetup

MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

此外,第三方框架(和应用程序)可以预先打包设置 说明,例如MockMvcConfigurer.Spring Framework 有一个这样的 内置实现,有助于在请求之间保存和重用 HTTP 会话。 您可以按如下方式使用它:spring-doc.cadn.net.cn

Java
// static import of SharedHttpSessionConfigurer.sharedHttpSession

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
        .apply(sharedHttpSession())
        .build();

// Use mockMvc to perform requests...
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

请参阅 javadoc 以获取ConfigurableMockMvcBuilder以获取所有 MockMvc 构建器功能的列表,或使用 IDE 浏览可用选项。spring-doc.cadn.net.cn

执行请求

本节介绍如何单独使用 MockMvc 来执行请求和验证响应。 如果通过WebTestClient请参阅相应的 编写测试 部分。spring-doc.cadn.net.cn

要执行使用任何 HTTP 方法的请求,如下例所示:spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.*

mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/hotels/{id}", 42) {
    accept = MediaType.APPLICATION_JSON
}

您还可以执行内部使用MockMultipartHttpServletRequest这样就不会实际解析 multipart 请求。相反,您必须将其设置为类似于以下示例:spring-doc.cadn.net.cn

Java
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
Kotlin
import org.springframework.test.web.servlet.multipart

mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}

您可以在 URI 模板样式中指定查询参数,如下例所示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
Kotlin
mockMvc.get("/hotels?thing={thing}", "somewhere")

您还可以添加表示 query 或 form 的 Servlet 请求参数 参数,如下例所示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/hotels") {
    param("thing", "somewhere")
}

如果应用程序代码依赖 Servlet 请求参数,并且不检查查询 string (通常是这种情况),使用哪个选项并不重要。 但请记住,随 URI 模板提供的查询参数将被解码 虽然请求参数通过param(…​)方法应该已经 被解码。spring-doc.cadn.net.cn

在大多数情况下,最好将上下文路径和 Servlet 路径保留在 请求 URI 的 URI 请求 URI如果您必须使用完整的请求 URI 进行测试,请务必将contextPathservletPath因此,请求映射可以正常工作,如下例所示 显示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}

在前面的示例中,将contextPathservletPath与每个执行的请求一起。相反,您可以设置默认请求 属性,如下例所示:spring-doc.cadn.net.cn

Java
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

上述属性会影响通过MockMvc实例。 如果在给定请求中也指定了相同的属性,它将覆盖默认值 价值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为 必须在每个请求中指定它们。spring-doc.cadn.net.cn

定义期望

您可以通过附加一个或多个andExpect(..)之后的调用 执行请求,如下例所示。一旦一个期望落空, 不会断言其他期望。spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/accounts/1").andExpect {
    status { isOk() }
}

您可以通过附加andExpectAll(..)执行 请求,如下例所示。与andExpect(..),andExpectAll(..)保证所有提供的期望都将被断言,并且 将跟踪和报告所有失败。spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpectAll(
    status().isOk(),
    content().contentType("application/json;charset=UTF-8"));

MockMvcResultMatchers.*提供了许多期望,其中一些是更进一步的 嵌套了更详细的期望。spring-doc.cadn.net.cn

期望分为两大类。第一类断言验证 响应的属性(例如,响应状态、标头和内容)。 这些是要断言的最重要的结果。spring-doc.cadn.net.cn

第二类断言超出了响应的范围。这些断言允许您 检查 Spring MVC 特定的方面,例如哪个控制器方法处理了 请求,是否引发并处理了异常,模型的内容是什么, 选择了什么视图,添加了哪些 Flash 属性,等等。他们还让您 检查 Servlet 特定的方面,比如 request 和 session 属性。spring-doc.cadn.net.cn

以下测试断言绑定或验证失败:spring-doc.cadn.net.cn

Java
mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andExpect {
    status { isOk() }
    model {
        attributeHasErrors("person")
    }
}

很多时候,在编写测试时,转储已执行的 请求。您可以按如下方式执行此作,其中print()是从MockMvcResultHandlers:spring-doc.cadn.net.cn

Java
mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status { isOk() }
        model {
            attributeHasErrors("person")
        }
    }

只要请求处理不会导致未经处理的异常,print()方法 将所有可用的结果数据打印到System.out.还有一个log()method 和 的print()方法,该方法接受OutputStream和 一个接受Writer.例如,调用print(System.err)打印结果 data 到System.err,在调用print(myWriter)将结果数据打印到自定义 作家。如果要记录结果数据而不是打印结果数据,可以调用log()方法,该方法将结果数据记录为单个DEBUGmessageorg.springframework.test.web.servlet.resultlogging 类别。spring-doc.cadn.net.cn

在某些情况下,您可能希望直接访问结果并验证 否则无法验证。这可以通过附加.andReturn()毕竟 其他期望,如下例所示:spring-doc.cadn.net.cn

Java
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
Kotlin
var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...

如果所有测试都重复相同的期望值,则可以在以下情况下设置一次通用期望值 构建MockMvcinstance,如下例所示:spring-doc.cadn.net.cn

Java
standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

请注意,通用期望始终适用,如果没有 创建单独的MockMvc实例。spring-doc.cadn.net.cn

当 JSON 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 JsonPath 表达式生成的链接,如下例所示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
Kotlin
mockMvc.get("/people") {
    accept(MediaType.APPLICATION_JSON)
}.andExpect {
    jsonPath("$.links[?(@.rel == 'self')].href") {
        value("http://localhost:8080/people")
    }
}

当 XML 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 XPath 表达式生成的链接:spring-doc.cadn.net.cn

Java
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Kotlin
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}
异步请求

本节介绍如何单独使用 MockMvc 来测试异步请求处理。 如果通过 WebTestClient 使用 MockMvc,则无需执行任何特殊作 异步请求的工作方式为WebTestClient自动执行所描述的作 在本节中。spring-doc.cadn.net.cn

Spring MVC 中支持的 Servlet 3.0 异步请求通过退出 Servlet 容器来工作 thread 并允许应用程序异步计算响应,之后 进行异步分派以完成对 Servlet 容器线程的处理。spring-doc.cadn.net.cn

在 Spring MVC Test 中,可以通过断言生成的 async 值来测试异步请求 首先,然后手动执行异步调度,最后验证响应。 下面是一个返回DeferredResult,Callable, 或反应式类型,例如 ReactorMono:spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) (1)
            .andExpect(request().asyncStarted()) (2)
            .andExpect(request().asyncResult("body")) (3)
            .andReturn();

    this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect(status().isOk()) (5)
            .andExpect(content().string("body"));
}
1 检查响应状态仍未更改
2 异步处理必须已启动
3 等待并断言异步结果
4 手动执行 ASYNC 调度(因为没有正在运行的容器)
5 验证最终响应
Kotlin
@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status { isOk() } (1)
        request { asyncStarted() } (2)
        // TODO Remove unused generic parameter
        request { asyncResult<Nothing>("body") } (3)
    }.andReturn()


    mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect {
                status { isOk() } (5)
                content().string("body")
            }
}
1 检查响应状态仍未更改
2 异步处理必须已启动
3 等待并断言异步结果
4 手动执行 ASYNC 调度(因为没有正在运行的容器)
5 验证最终响应
流式响应

您可以使用WebTestClient以测试流式处理响应,例如 Server-Sent Events。然而MockMvcWebTestClient不支持 Infinite 流,因为无法从客户端取消服务器流。 要测试无限流,您需要绑定到正在运行的服务器 或者在使用 Spring Boot 时,使用正在运行的服务器进行测试spring-doc.cadn.net.cn

筛选器注册

在设置MockMvc实例,您可以注册一个或多个 ServletFilter实例,如下例所示:spring-doc.cadn.net.cn

Java
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

已注册的过滤器通过MockFilterChainspring-test和 last 过滤器将DispatcherServlet.spring-doc.cadn.net.cn

MockMvc 与端到端测试

MockMVc 构建在 Servlet API 模拟实现之上,该实现来自spring-test模块,并且不依赖于正在运行的容器。因此,有 与使用实际 客户端和正在运行的实时服务器。spring-doc.cadn.net.cn

考虑这个问题的最简单方法是从空白开始MockHttpServletRequest. 无论你添加什么,请求都会变成什么。可能会让您感到意外的事情 是默认情况下没有上下文路径;不jsessionid饼干;无转发, error 或 async dispatches;因此,没有实际的 JSP 呈现。相反 “forwarded” 和 “redirected” URL 保存在MockHttpServletResponse并且可以 带着期望断言。spring-doc.cadn.net.cn

这意味着,如果您使用 JSP,则可以验证请求所指向的 JSP 页面 转发,但未呈现任何 HTML。换句话说,不调用 JSP。注意 但是,所有其他不依赖于转发的渲染技术(例如 Thymeleaf 和 Freemarker 按预期将 HTML 呈现到响应正文。同样的情况 用于呈现 JSON、XML 和其他格式@ResponseBody方法。spring-doc.cadn.net.cn

或者,您可以考虑从 Spring Boot 与@SpringBootTest.请参阅 Spring Boot 参考指南spring-doc.cadn.net.cn

每种方法都有优点和缺点。Spring MVC Test 中提供的选项包括 从经典的单元测试到完整的集成测试,规模上的站点各不相同。成为 当然,Spring MVC Test 中的任何选项都不属于经典单元的范畴 测试,但他们离它更近一些。例如,您可以隔离 Web 图层 通过将模拟服务注入控制器,在这种情况下,您正在测试 Web 层仅通过DispatcherServlet但是使用实际的 Spring 配置,就像你 可能会独立于其上方的层测试数据访问层。此外,您还可以使用 独立设置,一次专注于一个控制器,并手动提供 使其正常工作所需的配置。spring-doc.cadn.net.cn

使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的 tests 是服务器端的,因此如果出现异常,您可以检查使用了哪个处理程序 使用 HandlerExceptionResolver 处理,模型的内容是什么,绑定是什么 那里有错误,还有其他细节。这意味着编写 expectations 更容易, 因为服务器不是一个不透明的盒子,就像通过实际的 HTTP 测试它时一样 客户。这通常是经典单元测试的一个优势:它更容易编写, reason 和 debug 的 SET 的 SET 的 TEST 的 SET 的 TEST 的 SET 的 U 的 U在 同时,重要的是不要忽视一个事实,即反应是最 需要检查的重要事项。简而言之,这里有多种风格和策略的空间 的测试。spring-doc.cadn.net.cn

更多示例

框架自己的测试包括许多示例测试,旨在展示如何单独或通过 WebTestClient 使用 MockMvc。浏览这些示例以获得更多想法。spring-doc.cadn.net.cn

3.7.2. HtmlUnit 集成

Spring 提供了 MockMvcHtmlUnit 之间的集成。这简化了端到端测试的执行 使用基于 HTML 的视图时。通过此集成,您可以:spring-doc.cadn.net.cn

MockMvc 使用不依赖于 Servlet 容器的模板技术 (例如,Thymeleaf、FreeMarker 等),但它不适用于 JSP,因为 它们依赖于 Servlet 容器。
为什么选择 HtmlUnit 集成?

我想到的最明显的问题是“我为什么需要这个?答案是 最好通过浏览一个非常基本的示例应用程序来找到。假设您有一个 Spring MVC Web 支持在Message对象。该应用程序还 支持对所有消息进行分页。您将如何进行测试?spring-doc.cadn.net.cn

使用 Spring MVC Test,我们可以轻松测试是否能够创建一个Message如下:spring-doc.cadn.net.cn

Java
MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
@Test
fun test() {
    mockMvc.post("/messages/") {
        param("summary", "Spring Rocks")
        param("text", "In case you didn't know, Spring Rocks!")
    }.andExpect {
        status().is3xxRedirection()
        redirectedUrl("/messages/123")
    }
}

如果我们想测试允许我们创建消息的表单视图,该怎么办?例如 假设我们的表单类似于以下代码段:spring-doc.cadn.net.cn

<form id="messageForm" action="/messages/" method="post">
    <div class="pull-right"><a href="/messages/">Messages</a></div>

    <label for="summary">Summary</label>
    <input type="text" class="required" id="summary" name="summary" value="" />

    <label for="text">Message</label>
    <textarea id="text" name="text"></textarea>

    <div class="form-actions">
        <input type="submit" value="Create" />
    </div>
</form>

我们如何确保我们的表单生成正确的请求来创建新消息?一个 Naïve Attempt 可能类似于以下内容:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
Kotlin
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}

此测试有一些明显的缺点。如果我们更新控制器以使用参数message而不是text,我们的表单测试将继续通过,即使 HTML 表单 与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,因为 遵循:spring-doc.cadn.net.cn

Java
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
        .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());

MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param(summaryParamName, "Spring Rocks")
        .param(textParamName, "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='$summaryParamName']") { exists() }
    xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
    param(summaryParamName, "Spring Rocks")
    param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
    status().is3xxRedirection()
    redirectedUrl("/messages/123")
}

这将降低我们的测试错误通过的风险,但仍有一些 问题:spring-doc.cadn.net.cn

  • 如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂:Are 字段类型正确?字段是否已启用?等等。spring-doc.cadn.net.cn

  • 另一个问题是,我们所做的工作是预期的两倍。我们必须首先 验证视图,然后我们使用刚刚验证的相同参数提交视图。 理想情况下,可以一次性完成此作。spring-doc.cadn.net.cn

  • 最后,我们仍然无法解释一些事情。例如,如果表单具有 我们也希望测试的 JavaScript 验证?spring-doc.cadn.net.cn

总体问题是测试 Web 页面不涉及单个交互。 相反,它是用户如何与网页交互以及该 Web 如何交互的组合 page 与其他资源交互。例如,表单视图的结果用作 用于创建消息的用户的输入。此外,我们的表单视图可能会 使用影响页面行为的其他资源,例如 JavaScript 验证。spring-doc.cadn.net.cn

集成测试来救援?

为了解决前面提到的问题,我们可以执行端到端的集成测试, 但这有一些缺点。考虑测试允许我们分页浏览 消息。我们可能需要以下测试:spring-doc.cadn.net.cn

要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致许多其他挑战:spring-doc.cadn.net.cn

这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以将端到端集成测试的数量减少 重构我们的详细测试以使用运行更快、更可靠的 Mock 服务, 并且没有副作用。然后,我们可以实现少量的真正的端到端 集成测试,验证简单的工作流程,以确保一切协同工作 适当地。spring-doc.cadn.net.cn

进入 HtmlUnit 集成

那么,我们如何在测试页面的交互和仍然之间取得平衡 在我们的测试套件中保持良好的性能?答案是:“通过集成 MockMvc 替换为 HtmlUnit。spring-doc.cadn.net.cn

HtmlUnit 集成选项

当您想将 MockMvc 与 HtmlUnit 集成时,您有多种选择:spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit

本节介绍如何集成 MockMvc 和 HtmlUnit。如果需要,请使用此选项 以使用原始 HtmlUnit 库。spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit 设置

首先,确保您已包含net.sourceforge.htmlunit:htmlunit.为了将 HtmlUnit 与 Apache HttpComponents 一起使用 4.5+ 时,您需要使用 HtmlUnit 2.18 或更高版本。spring-doc.cadn.net.cn

我们可以轻松创建一个 HtmlUnitWebClient它与 MockMvc 集成,方法是使用MockMvcWebClientBuilder如下:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
这是一个使用MockMvcWebClientBuilder.对于高级用法, 看高深MockMvcWebClientBuilder.

这可确保引用localhost因为服务器被定向到我们的MockMvc实例,而无需真正的 HTTP 连接。任何其他 URL 为 像往常一样使用网络连接请求。这让我们可以轻松地测试 CDN 的spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit 用法

现在我们可以像往常一样使用 HtmlUnit,但不需要部署我们的 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息中包含以下内容:spring-doc.cadn.net.cn

Java
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Kotlin
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
默认上下文路径为 .或者,我们可以指定上下文路径 如""高深MockMvcWebClientBuilder.

一旦我们引用了HtmlPage,然后我们可以填写表格并提交 创建消息,如下例所示:spring-doc.cadn.net.cn

Java
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
Kotlin
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()

最后,我们可以验证是否已成功创建新消息。以下内容 断言使用 AssertJ 库:spring-doc.cadn.net.cn

Java
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
Kotlin
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")

前面的代码在许多方面改进了我们的 MockMvc 测试。 首先,我们不再需要显式验证我们的表单,然后创建一个请求 看起来像表格。相反,我们请求表单,填写并提交它,从而 显著降低开销。spring-doc.cadn.net.cn

另一个重要因素是 HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们还可以测试 JavaScript 在我们页面中的行为。spring-doc.cadn.net.cn

请参阅 HtmlUnit 文档 有关使用 HtmlUnit 的其他信息。spring-doc.cadn.net.cn

高深MockMvcWebClientBuilder

在到目前为止的示例中,我们使用了MockMvcWebClientBuilder以最简单的方式 可能,通过构建一个WebClient基于WebApplicationContext为我们加载 Spring TestContext 框架。以下示例中重复了此方法:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}

我们还可以指定其他配置选项,如下例所示:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build()
}

作为替代方案,我们可以通过配置MockMvc实例并将其提供给MockMvcWebClientBuilder如下:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

这更详细,但是,通过构建WebClient替换为MockMvc实例中,我们有 MockMvc 的全部功能触手可及。spring-doc.cadn.net.cn

有关创建MockMvc实例,请参阅 设置选项
MockMvc 和 WebDriver

在前面的部分中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API 的 API 中。在本节中,我们使用 Selenium WebDriver 中的其他抽象来使事情变得更加容易。spring-doc.cadn.net.cn

为什么选择 WebDriver 和 MockMvc?

我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver呢?这 Selenium WebDriver 提供了一个非常优雅的 API,让我们可以轻松地组织我们的代码。自 更好地展示它是如何工作的,我们在本节中探讨了一个示例。spring-doc.cadn.net.cn

尽管是 Selenium 的一部分,但 WebDriver 并没有 需要 Selenium Server 来运行您的测试。

假设我们需要确保正确创建一条消息。测试包括查找 HTML 表单输入元素,填写它们,并进行各种断言。spring-doc.cadn.net.cn

这种方法会导致许多单独的测试,因为我们想要测试错误条件 也。例如,我们希望确保如果我们只填写 表单。如果我们填写整个表单,则应显示新创建的消息 之后。spring-doc.cadn.net.cn

如果其中一个字段名为 “summary”,则可能会有类似于 在我们的测试中,在多个地方重复了以下内容:spring-doc.cadn.net.cn

Java
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
Kotlin
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)

那么,如果我们更改idsmmry?这样做会迫使我们更新所有 的测试中纳入此更改。这违反了 DRY 原则,所以我们应该 理想情况下,将此代码提取到其自己的方法中,如下所示:spring-doc.cadn.net.cn

Java
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}

public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}
Kotlin
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
    setSummary(currentPage, summary);
    // ...
}

fun setSummary(currentPage:HtmlPage , summary: String) {
    val summaryInput = currentPage.getHtmlElementById("summary")
    summaryInput.setValueAttribute(summary)
}

这样做可以确保在更改 UI 时不必更新所有测试。spring-doc.cadn.net.cn

我们甚至可以更进一步,将这个逻辑放在Object那 表示HtmlPage我们目前处于 on,如下例所示:spring-doc.cadn.net.cn

Java
public class CreateMessagePage {

    final HtmlPage currentPage;

    final HtmlTextInput summaryInput;

    final HtmlSubmitInput submit;

    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }

    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);

        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);

        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }

    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }

    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}
Kotlin
    class CreateMessagePage(private val currentPage: HtmlPage) {

        val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")

        val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")

        fun <T> createMessage(summary: String, text: String): T {
            setSummary(summary)

            val result = submit.click()
            val error = at(result)

            return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
        }

        fun setSummary(summary: String) {
            summaryInput.setValueAttribute(summary)
        }

        fun at(page: HtmlPage): Boolean {
            return "Create Message" == page.getTitleText()
        }
    }
}

以前,此模式称为 Page Object Pattern。虽然我们 当然可以用 HtmlUnit 来做到这一点,WebDriver 提供了一些工具,我们在 使此模式更易于实现。spring-doc.cadn.net.cn

MockMvc 和 WebDriver 设置

要将 Selenium WebDriver 与 Spring MVC 测试框架一起使用,请确保您的项目 包括org.seleniumhq.selenium:selenium-htmlunit-driver.spring-doc.cadn.net.cn

我们可以使用MockMvcHtmlUnitDriverBuilder如下例所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
这是一个使用MockMvcHtmlUnitDriverBuilder.对于更高级的 用法,请参阅高深MockMvcHtmlUnitDriverBuilder.

前面的示例可确保引用localhost作为服务器 定向到我们的MockMvc实例,而无需真正的 HTTP 连接。任何其他 像往常一样,使用网络连接请求 URL。这让我们可以轻松地测试 使用 CDN。spring-doc.cadn.net.cn

MockMvc 和 WebDriver 的使用

现在我们可以像往常一样使用 WebDriver,但不需要部署我们的 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息中包含以下内容:spring-doc.cadn.net.cn

Java
CreateMessagePage page = CreateMessagePage.to(driver);
Kotlin
val page = CreateMessagePage.to(driver)

然后,我们可以填写表单并提交它以创建消息,如下所示:spring-doc.cadn.net.cn

Java
ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
Kotlin
val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)

这通过利用 Page Object Pattern 改进了我们的 HtmlUnit 测试的设计。正如我们在 为什么是 WebDriver 和 MockMvc?中提到的,我们可以使用 Page 对象模式 使用 HtmlUnit,但使用 WebDriver 要容易得多。请考虑以下CreateMessagePage实现:spring-doc.cadn.net.cn

Java
public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;
    private WebElement text;

    (3)
    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
1 CreateMessagePage扩展了AbstractPage.我们不讨论AbstractPage,但总的来说,它包含我们所有页面的通用功能。 例如,如果我们的应用程序有一个导航栏、全局错误消息和其他 功能,我们可以将此 logic 放在共享位置。
2 我们所在的 HTML 页面的每个部分都有一个成员变量 感兴趣。这些是属于WebElement.WebDriver 的PageFactory让我们删除一个 来自 HtmlUnit 版本的CreateMessagePage通过自动解析 每WebElement.这PageFactory#initElements(WebDriver,Class<T>)方法会自动解析每个WebElement通过使用字段名称并查找它 由idname的元素。
3 我们可以使用@FindBy注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy注解来查找我们的提交按钮,并使用cssselector (input[type=submit]) 来获取。
Kotlin
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)

    (2)
    private lateinit var summary: WebElement
    private lateinit var text: WebElement

    (3)
    @FindBy(css = "input[type=submit]")
    private lateinit var submit: WebElement

    fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
        this.summary.sendKeys(summary)
        text.sendKeys(details)
        submit.click()
        return PageFactory.initElements(driver, resultPage)
    }
    companion object {
        fun to(driver: WebDriver): CreateMessagePage {
            driver.get("http://localhost:9990/mail/messages/form")
            return PageFactory.initElements(driver, CreateMessagePage::class.java)
        }
    }
}
1 CreateMessagePage扩展了AbstractPage.我们不讨论AbstractPage,但总的来说,它包含我们所有页面的通用功能。 例如,如果我们的应用程序有一个导航栏、全局错误消息和其他 功能,我们可以将此 logic 放在共享位置。
2 我们所在的 HTML 页面的每个部分都有一个成员变量 感兴趣。这些是属于WebElement.WebDriver 的PageFactory让我们删除一个 来自 HtmlUnit 版本的CreateMessagePage通过自动解析 每WebElement.这PageFactory#initElements(WebDriver,Class<T>)方法会自动解析每个WebElement通过使用字段名称并查找它 由idname的元素。
3 我们可以使用@FindBy注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy注解来查找我们的提交按钮,并使用cssselector (input[type=submit]) 来获取。

最后,我们可以验证是否已成功创建新消息。以下内容 断言使用 AssertJ 断言库:spring-doc.cadn.net.cn

Java
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
Kotlin
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")

我们可以看到,我们的ViewMessagePage让我们与我们的自定义域模型进行交互。为 示例中,它公开了一个返回Message对象:spring-doc.cadn.net.cn

Java
public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
Kotlin
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())

然后,我们可以在断言中使用丰富的域对象。spring-doc.cadn.net.cn

最后,我们不能忘记关闭WebDriver实例, 如下:spring-doc.cadn.net.cn

Java
@AfterEach
void destroy() {
    if (driver != null) {
        driver.close();
    }
}
Kotlin
@AfterEach
fun destroy() {
    if (driver != null) {
        driver.close()
    }
}

有关使用 WebDriver 的更多信息,请参阅 Selenium WebDriver 文档spring-doc.cadn.net.cn

高深MockMvcHtmlUnitDriverBuilder

在到目前为止的示例中,我们使用了MockMvcHtmlUnitDriverBuilder以最简单的方式 可能,通过构建一个WebDriver基于WebApplicationContext为我们加载 Spring TestContext 框架。此处重复此方法,如下所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}

我们还可以指定其他配置选项,如下所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build()
}

作为替代方案,我们可以通过配置MockMvc实例并将其提供给MockMvcHtmlUnitDriverBuilder如下:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

这更详细,但是,通过构建WebDriver替换为MockMvc实例中,我们有 MockMvc 的全部功能触手可及。spring-doc.cadn.net.cn

有关创建MockMvc实例,请参阅 设置选项
MockMvc 和 Geb

在上一节中,我们了解了如何将 MockMvc 与 WebDriver 结合使用。在本节中,我们将 使用 Geb 使我们的测试更加 Groovy-er。spring-doc.cadn.net.cn

为什么选择 Geb 和 MockMvc?

Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 WebDriver 的然而,Geb 通过处理一些 样板代码。spring-doc.cadn.net.cn

MockMvc 和 Geb 设置

我们可以轻松初始化 GebBrowser使用使用 MockMvc 的 Selenium WebDriver 作为 遵循:spring-doc.cadn.net.cn

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
这是一个使用MockMvcHtmlUnitDriverBuilder.对于更高级的 用法,请参阅高深MockMvcHtmlUnitDriverBuilder.

这可确保任何 URL 引用localhost因为服务器被定向到我们的MockMvc实例,而无需真正的 HTTP 连接。任何其他 URL 为 通过正常使用网络连接请求。这让我们可以轻松地测试 CDN 的spring-doc.cadn.net.cn

MockMvc 和 Geb 使用情况

现在我们可以像往常一样使用 Geb,但不需要将我们的应用程序部署到 一个 Servlet 容器。例如,我们可以请求视图创建一条消息,其中包含 以后:spring-doc.cadn.net.cn

to CreateMessagePage

然后,我们可以填写表单并提交它以创建消息,如下所示:spring-doc.cadn.net.cn

when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)

任何无法识别的方法调用或未找到的属性访问或引用都是 forwarded to the current page 对象。这删除了很多 直接使用 WebDriver 时需要。spring-doc.cadn.net.cn

与直接使用 WebDriver 一样,这通过使用 Page 对象改进了 HtmlUnit 测试的设计 模式。如前所述,我们可以将 Page Object Pattern 与 HtmlUnit 和 WebDriver 的,但使用 Geb 就更容易了。考虑我们新的基于 Groovy 的CreateMessagePage实现:spring-doc.cadn.net.cn

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

我们CreateMessagePage延伸Page.我们不讨论Page,但是,在 总结,它包含我们所有页面的通用功能。我们定义了一个 URL,其中 这个页面可以找到。这样,我们就可以导航到该页面,如下所示:spring-doc.cadn.net.cn

to CreateMessagePage

我们还有一个atclosure 来确定我们是否在指定的页面。它应该 返回true如果我们在正确的页面上。这就是为什么我们可以断言我们在 正确的页面,如下所示:spring-doc.cadn.net.cn

then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用 assertion,以便我们可以确定哪里出了问题 如果我们在错误的页面上。

接下来,我们创建一个contentclosed 指定 页。我们可以使用 jQuery 风格的 Navigator API 来选择我们感兴趣的内容。spring-doc.cadn.net.cn

最后,我们可以验证是否已成功创建新消息,如下所示:spring-doc.cadn.net.cn

then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

有关如何充分利用 Geb 的更多详细信息,请参阅 The Book of Geb 用户手册。spring-doc.cadn.net.cn

3.8. 测试客户端应用程序

您可以使用客户端测试来测试内部使用RestTemplate.这 这个想法是声明预期的请求并提供 “stub” 响应,以便您可以 专注于隔离测试代码 (即,不运行服务器) 。以下内容 示例展示了如何做到这一点:spring-doc.cadn.net.cn

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());

// Test code that uses the above RestTemplate ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())

// Test code that uses the above RestTemplate ...

mockServer.verify()

在前面的示例中,MockRestServiceServer(客户端 REST 的中心类 tests) 配置RestTemplate使用自定义ClientHttpRequestFactory那 根据预期断言实际请求并返回 “stub” 响应。在这个 case 中,我们希望请求/greeting并希望返回 200 响应text/plain内容。我们可以将其他预期请求和存根响应定义为 需要。当我们定义预期的请求和存根响应时,RestTemplate可以是 像往常一样在客户端代码中使用。在测试结束时,mockServer.verify()可以是 用于验证是否已满足所有期望。spring-doc.cadn.net.cn

默认情况下,请求应按照声明 expectations 的顺序进行。你 可以设置ignoreExpectOrder选项,在这种情况下,所有 检查 expectations 以查找给定请求的匹配项。这意味着 请求可以按任何顺序出现。以下示例使用ignoreExpectOrder:spring-doc.cadn.net.cn

Java
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
Kotlin
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()

即使默认情况下使用无序请求,每个请求也只允许运行一次。 这expectmethod 提供了一个重载的变体,该变体接受ExpectedCount指定计数范围的参数(例如once,manyTimes,max,min,between等)。以下示例使用times:spring-doc.cadn.net.cn

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());

// ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())

// ...

mockServer.verify()

请注意,当ignoreExpectOrder未设置(默认值),因此,请求 应按声明顺序排列,则该顺序仅适用于任何 预期请求。例如,如果 “/something” 需要两次,后跟 “/somewhere”三次,那么在出现之前应该有一个对 “/something” 的请求 对 “/somewhere” 的请求,但是,除了后面的 “/something” 和 “/somewhere” 之外, 请求可能随时出现。spring-doc.cadn.net.cn

作为上述所有方法的替代方案,客户端测试支持还提供了一个ClientHttpRequestFactory实现,您可以将其配置为RestTemplate自 将其绑定到MockMvc实例。这允许使用实际的服务器端处理请求 logic 的 logic,但没有运行 server。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...
Kotlin
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))

// Test code that uses the above RestTemplate ...

3.8.1. 静态导入

与服务器端测试一样,用于客户端测试的 Fluent API 需要一些静态 进口。这些很容易通过搜索找到MockRest*.Eclipse 用户应添加MockRestRequestMatchers.*MockRestResponseCreators.*如 “→→ Content Editor”下的 Eclipse 首选项中的 “favorite static members” 协助 → 收藏夹。这允许在键入 静态方法名称。其他 IDE(如 IntelliJ)可能不需要任何其他 配置。检查静态成员是否支持代码完成。spring-doc.cadn.net.cn

3.8.2. 客户端 REST 测试的更多示例

Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。spring-doc.cadn.net.cn

4. 更多资源

有关测试的更多信息,请参阅以下资源:spring-doc.cadn.net.cn