本章介绍了 Spring 对集成测试的支持和单元的最佳实践 测试。Spring 团队提倡测试驱动开发 (TDD)。Spring 团队拥有 发现正确使用控制反转 (IoC) 确实使两个单元 和集成测试更容易(因为存在 setter 方法和适当的 类的构造函数使它们更容易在测试中连接在一起,而不必这样做 设置服务定位器注册表和类似结构)。

1. 弹簧测试简介

测试是企业软件开发不可或缺的一部分。本章重点介绍 IoC 原则为单元测试增加的价值及其好处 Spring Framework 对集成测试的支持。(答 在企业中对测试的彻底处理超出了本参考的范围 手动。

2. 单元测试

依赖注入应该使代码对容器的依赖程度降低 与传统的 Java EE 开发保持一致。构成应用程序的 POJO 应 可在 JUnit 或 TestNG 测试中进行测试,使用运算符实例化对象,而无需 Spring 或任何其他容器。您可以使用模拟对象(与其他有价值的测试技术结合使用)来单独测试代码。 如果遵循 Spring 的架构建议,则生成的干净分层 代码库的组件化有助于简化单元测试。例如 您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象, 无需在运行单元测试时访问持久性数据。new

真正的单元测试通常运行速度极快,因为没有运行时基础结构 建立。强调真正的单元测试作为开发方法的一部分可以提高 您的生产力。您可能不需要测试章节的这一部分来帮助您编写 对基于 IoC 的应用程序进行有效的单元测试。对于某些单元测试方案, 但是,Spring Framework 提供了模拟对象和测试支持类,这些类 在本章中进行了介绍。

2.1. 模拟对象

Spring 包含许多专门用于模拟的包:

2.1.1. 环境

该包包含 和 抽象的模拟实现(请参见 Bean 定义配置文件和 PropertySource 抽象)。 并且对开发很有用 对依赖于特定于环境的属性的代码进行容器外测试。org.springframework.mock.envEnvironmentPropertySourceMockEnvironmentMockPropertySource

2.1.2. JNDI的

该软件包包含 JNDI 的部分实现 SPI,可用于为测试套件或独立设置简单的 JNDI 环境 应用。例如,如果 JDBC 实例绑定到同一个 JNDI 测试代码中的名称,就像在 Java EE 容器中的名称一样,您可以重用这两个应用程序代码 以及测试场景中的配置,无需修改。org.springframework.mock.jndiDataSource

软件包中的模拟 JNDI 支持是 从 Spring Framework 5.2 开始正式弃用,取而代之的是第三 Simple-JNDI等方。org.springframework.mock.jndi

2.1.3. Servlet 接口

该软件包包含一组全面的 Servlet API 用于测试 Web 上下文、控制器和筛选器的模拟对象。这些 mock 对象针对的是 Spring 的 Web MVC 框架,通常更 比动态模拟对象(如 EasyMock)更方便使用 或替代 Servlet API 模拟对象(例如 MockObjects)。org.springframework.mock.web

从 Spring Framework 5.0 开始,其中的模拟对象是 基于 Servlet 4.0 API。org.springframework.mock.web

Spring MVC Test 框架建立在模拟 Servlet API 对象之上,以提供 Spring MVC 的集成测试框架。请参阅 MockMvc

2.1.4. Spring Web 响应式

该包包含模拟实现 用于 WebFlux 应用程序。该软件包包含一个模拟 依赖于这些模拟请求和响应对象。org.springframework.mock.http.server.reactiveServerHttpRequestServerHttpResponseorg.springframework.mock.web.serverServerWebExchange

两者都是从同一个摘要扩展而来的 基类作为特定于服务器的实现,并与其共享行为。为 例如,模拟请求一旦创建就不可变,但您可以使用该方法 from 创建修改后的实例。MockServerHttpRequestMockServerHttpResponsemutate()ServerHttpRequest

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

WebTestClient 建立在模拟请求和响应之上,以支持 在没有 HTTP 服务器的情况下测试 WebFlux 应用程序。该客户端还可用于 使用正在运行的服务器进行端到端测试。

2.2. 单元测试支持类

Spring 包含许多可以帮助进行单元测试的类。它们分为两部分 类别:

2.2.1. 通用测试实用程序

该软件包包含多个通用实用程序 用于单元测试和集成测试。org.springframework.test.util

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

ReflectionTestUtils 是一个 基于反射的实用方法的集合。您可以在测试中使用这些方法 需要更改常量值、设置非字段、 调用非 setter 方法,或调用非配置或生命周期 在测试应用程序代码时的回调方法,例如:publicpublicpublic

  • ORM 框架(比如 JPA 和 Hibernate)宽恕或字段 访问,而不是域实体中属性的 setter 方法。privateprotectedpublic

  • Spring 对注解的支持(例如 、 和 ), 为 OR 字段、setter 方法、 和配置方法。@Autowired@Inject@Resourceprivateprotected

  • 使用注解,例如 和 用于生命周期回调 方法。@PostConstruct@PreDestroy

TestSocketUtils 是一个简单的 用于查找可用 TCP 端口以用于集成测试的实用程序 场景。localhost

TestSocketUtils可用于在 可用的随机端口。但是,这些实用程序不保证后续 给定端口的可用性,因此不可靠。建议不要使用服务器查找可用的本地端口,而是建议 您依赖于服务器在其选择或选择的随机临时端口上启动的能力 由操作系统分配。要与该服务器交互,您应该查询 服务器作为当前使用的端口。TestSocketUtils

2.2.2. Spring MVC测试实用程序

该包包含 ModelAndViewAssert,您可以将其 可以与 JUnit、TestNG 或任何其他测试框架结合使用以进行单元测试 处理 Spring MVC 对象。org.springframework.test.webModelAndView

对 Spring MVC 控制器进行单元测试
要将 Spring MVC 类作为 POJO 进行单元测试,请结合使用 Spring 的 Servlet API 模拟中的 、 等。用于对 Spring MVC 和 REST 类与 Spring MVC 的配置结合使用,请改用 Spring MVC 测试框架ControllerModelAndViewAssertMockHttpServletRequestMockHttpSessionControllerWebApplicationContext

3. 集成测试

本节(本章其余大部分内容)介绍了 Spring 的集成测试 应用。它包括以下主题:

3.1. 概述

重要的是能够在不需要的情况下执行一些集成测试 部署到应用程序服务器或连接到其他企业基础架构。 这样做可以测试以下内容:

  • Spring IoC 容器上下文的正确连接。

  • 使用 JDBC 或 ORM 工具进行数据访问。这可以包括正确性等内容 SQL 语句、Hibernate 查询、JPA 实体映射等。

Spring Framework 为模块中的集成测试提供了一流的支持。实际 JAR 文件的名称可能包含发行版本 也可能是长形式,具体取决于您获得的位置 它来自(有关说明,请参阅依赖项管理部分)。该库包含以下包,该包 包含用于与 Spring 容器集成测试的有价值的类。此测试 不依赖于应用程序服务器或其他部署环境。此类测试是 运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者 依赖于部署到应用程序服务器的远程测试。spring-testorg.springframework.testorg.springframework.test

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

3.2. 集成测试的目标

Spring 的集成测试支持具有以下主要目标:

接下来的几节将介绍每个目标,并提供实现和 配置详细信息。

3.2.1. 上下文管理和缓存

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

测试类通常为 XML 或 Groovy 声明资源位置数组 配置元数据(通常位于类路径中)或组件类数组 用于配置应用程序。这些位置或类与 或 类似于生产文件中指定的配置文件或其他配置文件 部署。web.xml

默认情况下,一旦加载,配置的内容将重复用于每个测试。 因此,每个测试套件仅产生一次设置成本,随后的测试执行 要快得多。在此上下文中,术语“测试套件”表示所有测试都在同一 JVM — 例如,所有测试都从给定项目的 Ant、Maven 或 Gradle 构建中运行 或模块。在极少数情况下,测试会损坏应用程序上下文并需要 重新加载(例如,通过修改 Bean 定义或应用程序的状态 object) 可以将 TestContext 框架配置为重新加载配置,并且 在执行下一个测试之前重新生成应用程序上下文。ApplicationContext

请参阅上下文管理和上下文缓存,以及 TestContext 框架。

3.2.2. 测试夹具的依赖注入

当 TestContext 框架加载应用程序上下文时,它可以选择 使用依赖关系注入配置测试类的实例。这提供了一个 使用预配置的 Bean 设置测试夹具的便捷机制 应用程序上下文。这里的一个很大好处是可以重用应用程序上下文 跨各种测试场景(例如,用于配置 Spring 管理的对象 图、事务代理、实例等),从而避免了 需要为单个测试用例复制复杂的测试夹具设置。DataSource

例如,考虑一个场景,我们有一个类 () 实现域实体的数据访问逻辑。我们想写 测试以下方面的集成测试:HibernateTitleRepositoryTitle

  • Spring 配置:基本上,与 Bean 配置相关的一切都是否正确且存在?HibernateTitleRepository

  • Hibernate 映射文件配置:是否映射正确,是否 正确的延迟加载设置到位?

  • 的逻辑是: 是否配置了此类的实例 表现是否符合预期?HibernateTitleRepository

请参阅使用 TestContext 框架注入测试夹具的依赖项。

3.2.3. 事务管理

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

TestContext 框架解决了这个问题。默认情况下,框架会创建和 为每个测试回滚事务。您可以编写可以假定存在的代码 的交易。如果在测试中调用事务代理对象,则它们的行为 根据其配置的事务语义正确。此外,如果测试 方法在事务中运行时删除所选表的内容 管理测试时,事务默认回滚,数据库返回到 它在执行测试之前的状态。事务支持由以下机构为测试提供 使用在测试的应用程序上下文中定义的 bean。PlatformTransactionManager

如果您希望提交事务(不寻常,但当您需要 特定的测试来填充或修改数据库),你可以告诉 TestContext 框架,通过使用 @Commit 注释使事务提交而不是回滚。

请参阅使用 TestContext 框架进行事务管理。

3.2.4. 集成测试的支持类

Spring TestContext 框架提供了几个支持类,这些类 简化集成测试的编写。这些基本测试类提供明确定义的 挂钩到测试框架以及方便的实例变量和方法, 它允许您访问:abstract

  • 用于执行显式 Bean 查找或测试 整个上下文。ApplicationContext

  • A ,用于执行SQL语句查询数据库。您可以使用这样的 在执行与数据库相关的操作之前和之后确认数据库状态的查询 应用程序代码,Spring 确保此类查询在相同的范围内运行 事务作为应用程序代码。当与 ORM 工具结合使用时,请确保 以避免误报JdbcTemplate

此外,您可能希望创建自己的自定义应用程序范围的超类 特定于项目的实例变量和方法。

请参阅 TestContext 框架的支持类。

3.3. JDBC测试支持

该软件包包含 ,这是一个 与JDBC相关的实用程序函数的集合,旨在简化标准数据库 测试方案。具体而言,提供以下静态实用程序 方法。org.springframework.test.jdbcJdbcTestUtilsJdbcTestUtils

  • countRowsInTable(..):计算给定表中的行数。

  • countRowsInTableWhere(..):使用 provided 条款。WHERE

  • deleteFromTables(..):删除指定表中的所有行。

  • deleteFromTableWhere(..):使用提供的子句从给定表中删除行。WHERE

  • dropTables(..):删除指定的表。

AbstractTransactionalJUnit4SpringContextTests AbstractTransactionalTestNGSpringContextTests 提供了方便的方法,这些方法委托给 中的上述方法。JdbcTestUtils

该模块支持配置和启动嵌入式 数据库,可用于与数据库交互的集成测试。 有关详细信息,请参阅嵌入式数据库 支持测试数据访问 具有嵌入式数据库的逻辑spring-jdbc

3.4. 注解

本节介绍在测试 Spring 应用程序时可以使用的注释。 它包括以下主题:

3.4.1. Spring 测试注解

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

Spring 的测试注解包括以下内容:

@BootstrapWith

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

@ContextConfiguration

@ContextConfiguration定义用于确定如何 加载和配置集成测试。具体来说,声明应用程序上下文资源或 组件。ApplicationContext@ContextConfigurationlocationsclasses

资源位置通常是 XML 配置文件或 Groovy 脚本,位于 类路径,而组件类通常是类。然而 资源位置还可以引用文件系统和组件中的文件和脚本 类可以是类、类等。有关详细信息,请参阅组件类@Configuration@Component@Service

下面的示例演示引用 XML 的批注 文件:@ContextConfiguration

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

下面的示例演示引用类的批注:@ContextConfiguration

爪哇岛
Kotlin
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
1 引用类。

作为声明资源位置或组件类的替代或补充, 可用于声明类。 以下示例演示了这种情况:@ContextConfigurationApplicationContextInitializer

爪哇岛
Kotlin
@ContextConfiguration(initializers = CustomContextInitializer.class) (1)
class ContextInitializerTests {
    // class body...
}
1 声明初始值设定项类。

您可以选择使用将策略声明为 井。但请注意,您通常不需要显式配置加载程序。 由于默认加载器支持 和 资源或 元件。@ContextConfigurationContextLoaderinitializerslocationsclasses

以下示例同时使用位置和加载程序:

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

有关更多详细信息,请参阅上下文管理@Nested测试类配置和 javadocs。@ContextConfiguration

@WebAppConfiguration

@WebAppConfiguration是一个类级注释,可用于声明集成测试的 load 应为 . 仅存在 on 测试类即可确保为测试加载 a,使用默认值 作为 Web 应用程序根目录的路径(即 资源库路径)。资源库路径在后台用于创建一个 ,它用作测试的 .ApplicationContextWebApplicationContext@WebAppConfigurationWebApplicationContext"file:src/main/webapp"MockServletContextServletContextWebApplicationContext

以下示例演示如何使用注释:@WebAppConfiguration

爪哇岛
Kotlin
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}

要覆盖默认值,您可以使用 隐式属性。和资源前缀都是 支持。如果未提供资源前缀,则假定路径为文件系统 资源。下面的示例演示如何指定类路径资源:valueclasspath:file:

爪哇岛
Kotlin
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
1 指定类路径资源。

请注意,必须在单个测试类或测试类中与 结合使用 等级制度。有关更多详细信息,请参阅 javadoc @WebAppConfiguration@WebAppConfiguration@ContextConfiguration

@ContextHierarchy

@ContextHierarchy是类级注释,用于定义集成测试的实例层次结构。 应该是 使用一个或多个实例的列表声明,每个实例 在上下文层次结构中定义一个级别。以下示例演示了在单个测试类中使用 ( 也可以使用 在测试类层次结构中):ApplicationContext@ContextHierarchy@ContextConfiguration@ContextHierarchy@ContextHierarchy

爪哇岛
Kotlin
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
    // class body...
}
爪哇岛
Kotlin
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
    // class body...
}

如果需要合并或覆盖给定上下文级别的配置 层次结构 在测试类层次结构中,必须通过提供 每个对应的属性值相同 级别。请参见上下文层次结构和 javadoc @ContextHierarchy 有关更多示例。name@ContextConfiguration

@ActiveProfiles

@ActiveProfiles是用于声明哪个 Bean 的类级注释 定义配置文件在加载 for 时应处于活动状态 集成测试。ApplicationContext

以下示例指示配置文件应处于活动状态:dev

爪哇岛
Kotlin
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
1 指示配置文件应处于活动状态。dev

以下示例指示 和 配置文件都应 保持活跃:devintegration

爪哇岛
Kotlin
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
    // class body...
}
1 指示 和 配置文件应处于活动状态。devintegration
@ActiveProfiles提供对继承活动 Bean 定义概要文件的支持 由超类声明,默认情况下由封闭类声明。您还可以解析活动 Bean 定义配置文件通过实现自定义 ActiveProfilesResolver 并使用 的属性进行注册,以编程方式进行概要分析。resolver@ActiveProfiles

请参阅使用环境配置文件进行上下文配置@Nested测试类配置@ActiveProfiles javadoc 示例和更多详细信息。

@TestPropertySource

@TestPropertySource是一个类级注释,可用于配置 要添加到 for an 中加载的属性文件和内联属性的位置 集成测试。PropertySourcesEnvironmentApplicationContext

下面的示例演示如何从类路径声明属性文件:

爪哇岛
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 从类路径的根目录中获取属性。test.properties

下面的示例演示如何声明内联属性:

爪哇岛
Kotlin
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
    // class body...
}
1 声明和属性。timezoneport

有关示例和更多详细信息,请参阅使用测试属性源进行上下文配置

@DynamicPropertySource

@DynamicPropertySource是一个方法级注释,可用于注册要添加到 for 中的 集合的动态属性 为集成测试加载。动态属性很有用 当您事先不知道属性的值时,例如,如果属性 由外部资源管理,例如由 Testcontainers 项目管理的容器。PropertySourcesEnvironmentApplicationContext

下面的示例演示如何注册动态属性:

爪哇岛
Kotlin
@ContextConfiguration
class MyIntegrationTests {

    static MyExternalServer server = // ...

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

    // tests ...
}
1 用 注释方法。static@DynamicPropertySource
2 接受 a 作为参数。DynamicPropertyRegistry
3 注册要从服务器延迟检索的动态属性。server.port

有关详细信息,请参阅使用动态属性源进行上下文配置

@DirtiesContext

@DirtiesContext表示底层 Spring 已经 在执行测试期间被弄脏(即,测试在 以某种方式 — 例如,通过改变单例 Bean 的状态),并且应该是 闭。当应用程序上下文被标记为脏时,它将从测试中删除 框架的缓存并关闭。因此,底层 Spring 容器是 为需要具有相同配置的上下文的任何后续测试重新生成 元数据。ApplicationContext

您可以同时用作类级和方法级注释 相同的类或类层次结构。在这种情况下,标记 在任何此类注释方法之前或之后以及当前之前或之后都是肮脏的 test 类,具体取决于配置的 和 。@DirtiesContextApplicationContextmethodModeclassMode

以下示例说明了何时会为各种上下文弄脏 配置方案:

  • 在当前测试类之前,当在类模式设置为 的类上声明时。BEFORE_CLASS

    爪哇岛
    Kotlin
    @DirtiesContext(classMode = BEFORE_CLASS) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在当前测试类之前弄脏上下文。
  • 在当前测试类之后,当在类模式设置为(即默认类模式)的类上声明时。AFTER_CLASS

    爪哇岛
    Kotlin
    @DirtiesContext (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 在当前测试类之后弄脏上下文。
  • 在当前测试类中的每个测试方法之前,当在类上声明时 mode 设置为BEFORE_EACH_TEST_METHOD.

    爪哇岛
    Kotlin
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 在每个测试方法之前弄脏上下文。
  • 在当前测试类中的每个测试方法之后,当在类上声明时 mode 设置为AFTER_EACH_TEST_METHOD.

    爪哇岛
    Kotlin
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 在每个测试方法之后弄脏上下文。
  • 在当前测试之前,在方法模式设置为 的方法上声明时。BEFORE_METHOD

    爪哇岛
    Kotlin
    @DirtiesContext(methodMode = BEFORE_METHOD) (1)
    @Test
    void testProcessWhichRequiresFreshAppCtx() {
        // some logic that requires a new Spring container
    }
    1 在当前测试方法之前弄脏上下文。
  • 在当前测试之后,在方法模式设置为(即默认方法模式)的方法上声明时。AFTER_METHOD

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

如果在测试中使用,其上下文配置为上下文的一部分 层次结构,可以使用标志来控制如何 上下文缓存被清除。默认情况下,使用穷举算法来清除 上下文缓存,不仅包括当前级别,还包括所有其他上下文 共享当前测试通用的祖先上下文的层次结构。驻留在共同祖先的子层次结构中的所有实例 context 将从上下文缓存中删除并关闭。如果穷举算法是 对于特定用例,您可以指定更简单的电流电平算法, 如以下示例所示。@DirtiesContext@ContextHierarchyhierarchyModeApplicationContext

爪哇岛
Kotlin
@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 使用当前级别的算法。

有关 和 算法的更多详细信息,请参阅 DirtiesContext.HierarchyMode javadoc。EXHAUSTIVECURRENT_LEVEL

@TestExecutionListeners

@TestExecutionListeners用于为特定测试类注册侦听器,其 子类及其嵌套类。如果您希望在全球范围内注册一个侦听器,则 应通过 TestExecutionListener 配置中描述的自动发现机制注册它。

下面的示例演示如何注册两个实现:TestExecutionListener

爪哇岛
Kotlin
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
1 注册两个实现。TestExecutionListener

默认情况下,支持从 超类或封闭类。有关示例和更多详细信息,请参阅@Nested测试类配置javadoc @TestExecutionListeners。如果您发现需要切换 返回到使用默认实现,请参阅注释 在注册 TestExecutionListener 实现中。@TestExecutionListenersTestExecutionListener

@RecordApplicationEvents

@RecordApplicationEvents是一个类级注释,用于指示 Spring TestContext Framework 记录在单个测试执行期间发布的所有应用程序事件。ApplicationContext

记录的事件可以通过测试中的 API 访问。ApplicationEvents

有关示例和更多详细信息,请参阅应用程序事件javadoc @RecordApplicationEvents

@Commit

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

以下示例演示如何使用注释:@Commit

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

@Rollback指示事务测试方法的事务是否应 测试方法完成后回滚。如果 ,则滚动事务 返回。否则,将提交事务(另请参见 @Commit)。Spring 中集成测试的回滚 TestContext Framework 默认为 even 即使未显式声明。truetrue@Rollback

声明为类级批注时,定义默认回滚 测试类层次结构中所有测试方法的语义。当声明为 方法级注解,定义特定测试的回滚语义 方法,可能会覆盖类级别或语义。@Rollback@Rollback@Rollback@Commit

以下示例导致测试方法的结果不回滚(即 结果提交到数据库):

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

@BeforeTransaction指示带注释的方法应在 事务已启动,对于已配置为在 事务。 方法 不需要,并且可以在基于 Java 8 的接口上声明 方法。void@Transactional@BeforeTransactionpublic

以下示例演示如何使用注释:@BeforeTransaction

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

@AfterTransaction指示带注释的方法应在 事务已结束,对于已配置为在 事务。 方法 不需要,并且可以在基于 Java 8 的接口上声明 方法。void@Transactional@AfterTransactionpublic

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

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

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

有关详细信息,请参阅使用 @Sql 以声明方式执行 SQL 脚本

@SqlConfig

@SqlConfig定义用于确定如何分析和运行 SQL 脚本的元数据 使用注释进行配置。以下示例演示如何使用它:@Sql

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

@SqlMergeMode用于对测试类或测试方法进行注释,以配置是否 方法级声明与类级声明合并。如果未在测试类或测试方法上声明,则合并模式 将默认使用。使用该模式,方法级声明将 有效地重写类级声明。@Sql@Sql@SqlMergeModeOVERRIDEOVERRIDE@Sql@Sql

请注意,方法级声明将重写类级声明。@SqlMergeMode

下面的示例演示如何在类级别使用。@SqlMergeMode

爪哇岛
Kotlin
@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

下面的示例演示如何在方法级别使用。@SqlMergeMode

爪哇岛
Kotlin
@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
@SqlGroup

@SqlGroup是聚合多个批注的容器批注。您可以 本机使用来声明多个嵌套注解,也可以使用它 结合 Java 8 对可重复注解的支持,可以 在同一类或方法上多次声明,隐式生成此容器 注解。以下示例演示如何声明 SQL 组:@Sql@SqlGroup@Sql@Sql

爪哇岛
Kotlin
@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 脚本。

3.4.2. 标准注解支持

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

  • @Autowired

  • @Qualifier

  • @Value

  • @Resource(javax.annotation),如果存在 JSR-250

  • @ManagedBean(javax.annotation),如果存在 JSR-250

  • @Inject(javax.inject),如果存在 JSR-330

  • @Named(javax.inject),如果存在 JSR-330

  • @PersistenceContext(javax.persistence),如果存在 JPA

  • @PersistenceUnit(javax.persistence),如果存在 JPA

  • @Required

  • @Transactional(org.springframework.transaction.annotation),具有有限的属性支持

JSR-250 生命周期注解

在 Spring TestContext 框架中,您可以使用和 在 中配置的任何应用程序组件上的标准语义。 但是,这些生命周期注释在实际测试类中的使用受到限制。@PostConstruct@PreDestroyApplicationContext

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

3.4.3. Spring JUnit 4 测试注解

@IfProfileValue

@IfProfileValue指示已为特定测试启用带注释的测试 环境。如果 configured 返回 ,则启用测试。否则,测试将被禁用,并且实际上, 忽视。ProfileValueSourcevaluename

您可以在类级别和/或方法级别进行应用。 对于任何 的类级用法,其优先级优先于方法级用法 该类或其子类中的方法。具体而言,如果符合以下条件,则启用测试 在类级别和方法级别都启用。缺少 意味着隐式启用测试。这类似于 JUnit 4 注解的语义,只不过 always 的存在会禁用测试。@IfProfileValue@IfProfileValue@IfProfileValue@Ignore@Ignore

以下示例显示了一个具有注释的测试:@IfProfileValue

爪哇岛
Kotlin
@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”时,才运行此测试。

或者,您可以使用(带有语义)列表进行配置,以在 JUnit 4 环境中实现对测试组的类似 TestNG 的支持。 请看以下示例:@IfProfileValuevaluesOR

爪哇岛
Kotlin
@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 为单元测试和集成测试运行此测试。
@ProfileValueSourceConfiguration

@ProfileValueSourceConfiguration是指定类型的类级批注 在检索通过注释配置的配置文件值时使用。如果未为 test,默认使用。下面的示例演示如何 用:ProfileValueSource@IfProfileValue@ProfileValueSourceConfigurationSystemProfileValueSource@ProfileValueSourceConfiguration

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

@Timed指示带批注的测试方法必须在指定的 时间段(以毫秒为单位)。如果文本执行时间超过指定时间 期间,测试失败。

该时间段包括运行测试方法本身、测试的任何重复(见),以及测试夹具的任何设置或拆卸。以下 示例演示如何使用它:@Repeat

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

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

@Repeat

@Repeat指示必须重复运行带批注的测试方法。数量 测试方法的运行时间在注释中指定。

要重复的执行范围包括测试方法本身的执行,如 以及测试夹具的任何设置或拆卸。与 SpringMethodRule 一起使用时,该范围还包括 通过实现准备测试实例。这 以下示例演示如何使用注释:TestExecutionListener@Repeat

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

3.4.4. Spring JUnit Jupiter 测试注解

SpringExtension 和 JUnit Jupiter 结合使用时,支持以下注解 (即 JUnit 5 中的编程模型):

@SpringJUnitConfig

@SpringJUnitConfig是一个组合注解,它结合了 JUnit Jupiter 和 Spring TestContext 框架。它可以在班级级别用作临时 替换为 。关于配置选项,唯一的 和 之间的区别是那个组件 可以使用 中的属性声明类。@ExtendWith(SpringExtension.class)@ContextConfiguration@ContextConfiguration@ContextConfiguration@SpringJUnitConfigvalue@SpringJUnitConfig

以下示例演示如何使用注释指定 配置类:@SpringJUnitConfig

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

以下示例演示如何使用注释来指定 配置文件的位置:@SpringJUnitConfig

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

有关@SpringJUnitConfig和更多详细信息,请参阅上下文管理以及 javadoc。@ContextConfiguration

@SpringJUnitWebConfig

@SpringJUnitWebConfig是一个组合注解,它结合了 JUnit Jupiter 和 Spring TestContext 框架。您可以在课堂上使用它 级别作为 和 的直接替代品。 关于配置选项,和之间的唯一区别是可以使用 中的属性来声明组件类。此外,只能通过使用 中的属性来覆盖 中的属性。@ExtendWith(SpringExtension.class)@ContextConfiguration@WebAppConfiguration@ContextConfiguration@WebAppConfiguration@ContextConfiguration@SpringJUnitWebConfigvalue@SpringJUnitWebConfigvalue@WebAppConfigurationresourcePath@SpringJUnitWebConfig

以下示例演示如何使用注释指定 配置类:@SpringJUnitWebConfig

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

以下示例演示如何使用注释来指定 配置文件的位置:@SpringJUnitWebConfig

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

@TestConstructor是一个类型级注释,用于配置参数 的测试类构造函数是从测试的 .ApplicationContext

如果测试类中不存在或元存在,则默认测试 将使用构造函数 AutoWire 模式。有关如何更改的详细信息,请参阅下面的提示 默认模式。但是请注意,本地声明 on a 构造函数优先于两者和默认模式。@TestConstructor@Autowired@TestConstructor

更改默认测试构造函数自动连线模式

可以通过将 JVM 系统属性设置为 来更改缺省测试构造函数自动连线模式。或者, 默认模式可以通过 SpringProperties 机制进行设置。spring.test.constructor.autowire.modeall

从 Spring Framework 5.3 开始,默认模式也可以配置为 JUnit Platform 配置参数

如果未设置该属性,则测试类 构造函数不会自动连接。spring.test.constructor.autowire.mode

从 Spring Framework 5.2 开始,仅支持结合使用 与 JUnit Jupiter 一起使用。请注意,是 通常会自动为您注册 - 例如,当使用注释(例如 和/或各种与测试相关的注释)时 Spring Boot 测试。@TestConstructorSpringExtensionSpringExtension@SpringJUnitConfig@SpringJUnitWebConfig
@NestedTestConfiguration

@NestedTestConfiguration是一个类型级批注,用于配置如何 Spring 测试配置注释在封闭的类层次结构中进行处理 用于内部测试类。

如果在测试类上不存在或元存在,则在其 超类型层次结构,或在其封闭类层次结构中,默认封闭 将使用配置继承模式。有关如何操作的详细信息,请参阅下面的提示 更改默认模式。@NestedTestConfiguration

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

默认的封闭配置继承模式是 ,但它可以是 通过将 JVM 系统属性设置为 进行更改。或者,可以通过 SpringProperties 机制设置默认模式。INHERITspring.test.enclosing.configurationOVERRIDE

Spring TestContext 框架支持 以下注释。@NestedTestConfiguration

通常只有结合使用才有意义 在 JUnit Jupiter 中使用测试类;但是,可能还有其他检查 支持 Spring 的框架和利用它的嵌套测试类 注解。@NestedTestConfiguration@Nested

有关示例和进一步内容,请参阅@Nested测试类配置 详。

@EnabledIf

@EnabledIf用于表示带注释的 JUnit Jupiter 测试类或测试方法 已启用,如果提供的计算结果为 。 具体而言,如果表达式的计算结果为或等于(忽略大小写),则启用测试。当应用于类级别时,所有测试方法 默认情况下,该类也会自动启用。expressiontrueBoolean.TRUEStringtrue

表达式可以是以下任何一种:

  • Spring 表达式语言 (SpEL) 表达式。例如:@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")

  • Spring 环境中可用属性的占位符。 例如:@EnabledIf("${smoke.tests.enabled}")

  • 文本文本。例如:@EnabledIf("true")

但是,请注意,文本文本不是动态解析的结果 属性占位符的实用价值为零,因为 等同于,在逻辑上毫无意义。@EnabledIf("false")@Disabled@EnabledIf("true")

您可以用作元注释来创建自定义组合注释。为 例如,您可以按如下方式创建自定义注解:@EnabledIf@EnabledOnMac

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

@EnabledOnMac只是作为一个例子,说明什么是可能的。如果你有那个确切的 用例,请使用 JUnit Jupiter 中的内置支持。@EnabledOnOs(MAC)

从 JUnit 5.7 开始,JUnit Jupiter 也有一个名为 .因此 如果您希望使用 Spring 的支持,请确保导入注解类型 从正确的包装。@EnabledIf@EnabledIf

@DisabledIf

@DisabledIf用于表示带注释的 JUnit Jupiter 测试类或测试 方法被禁用,如果提供的计算结果为 。具体而言,如果表达式的计算结果等于或等于 to(忽略大小写),则禁用测试。在类级别应用时,所有 该类中的测试方法也会自动禁用。expressiontrueBoolean.TRUEStringtrue

表达式可以是以下任何一种:

  • Spring 表达式语言 (SpEL) 表达式。例如:@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")

  • Spring 环境中可用属性的占位符。 例如:@DisabledIf("${smoke.tests.disabled}")

  • 文本文本。例如:@DisabledIf("true")

但是,请注意,文本文本不是动态解析的结果 属性占位符的实用价值为零,因为 等同于,在逻辑上毫无意义。@DisabledIf("true")@Disabled@DisabledIf("false")

您可以用作元注释来创建自定义组合注释。为 例如,您可以按如下方式创建自定义注解:@DisabledIf@DisabledOnMac

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

@DisabledOnMac只是作为一个例子,说明什么是可能的。如果你有那个确切的 用例,请使用 JUnit Jupiter 中的内置支持。@DisabledOnOs(MAC)

从 JUnit 5.7 开始,JUnit Jupiter 也有一个名为 .因此 如果您希望使用 Spring 的支持,请确保导入注解类型 从正确的包装。@DisabledIf@DisabledIf

3.4.5. 测试的元注解支持

您可以将大多数与测试相关的注释用作元注释来创建自定义组合 注释并减少测试套件中的配置重复。

您可以将以下各项作为元注释与 TestContext 框架结合使用。

  • @BootstrapWith

  • @ContextConfiguration

  • @ContextHierarchy

  • @ActiveProfiles

  • @TestPropertySource

  • @DirtiesContext

  • @WebAppConfiguration

  • @TestExecutionListeners

  • @Transactional

  • @BeforeTransaction

  • @AfterTransaction

  • @Commit

  • @Rollback

  • @Sql

  • @SqlConfig

  • @SqlMergeMode

  • @SqlGroup

  • @Repeat (仅在 JUnit 4 上受支持)

  • @Timed (仅在 JUnit 4 上受支持)

  • @IfProfileValue (仅在 JUnit 4 上受支持)

  • @ProfileValueSourceConfiguration (仅在 JUnit 4 上受支持)

  • @SpringJUnitConfig (仅在 JUnit Jupiter 上受支持)

  • @SpringJUnitWebConfig (仅在 JUnit Jupiter 上受支持)

  • @TestConstructor (仅在 JUnit Jupiter 上受支持)

  • @NestedTestConfiguration (仅在 JUnit Jupiter 上受支持)

  • @EnabledIf (仅在 JUnit Jupiter 上受支持)

  • @DisabledIf (仅在 JUnit Jupiter 上受支持)

请看以下示例:

爪哇岛
Kotlin
@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 { }

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

爪哇岛
Kotlin
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }

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

爪哇岛
Kotlin
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

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

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

爪哇岛
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 的通用测试配置的注解, 如下:

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

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

爪哇岛
Kotlin
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }

由于 JUnit Jupiter 支持使用 、 、 、 和其他作为元注释,您还可以在 测试方法级别。例如,如果我们希望创建一个组合注释,该注释结合 来自 JUnit Jupiter 的注解和来自 Spring 的注解,我们可以创建一个注解,因为 遵循:@Test@RepeatedTestParameterizedTest@Test@Tag@Transactional@TransactionalIntegrationTest

爪哇岛
Kotlin
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }

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

爪哇岛
Kotlin
@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }

有关详细信息,请参阅 Spring Annotation Programming Model wiki 页面。

3.5. Spring TestContext 框架

Spring TestContext Framework(位于包中)提供通用的、注释驱动的单元和集成测试支持,即 与正在使用的测试框架无关。TestContext 框架还放置了一个很好的 约定比配置更重要,您的合理默认值 可以通过基于注释的配置进行覆盖。org.springframework.test.context

除了通用测试基础结构之外,TestContext 框架还提供 显式支持 JUnit 4、JUnit Jupiter(又名 JUnit 5)和 TestNG。对于 JUnit 4 和 TestNG,Spring 提供了支持类。此外,Spring 还提供了一个自定义 JUnit 和自定义 JUnit 4 的 JUnit 以及 JUnit 的自定义 Jupiter 让你编写所谓的 POJO 测试类。POJO 测试类不是 需要扩展特定类层次结构,例如支持类。abstractRunnerRulesExtensionabstract

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

3.5.1. 关键抽象

框架的核心由类、、和接口组成。为每个测试类创建一个(例如,用于执行 JUnit Jupiter 中单个测试类中的所有测试方法)。这 反过来,管理保存当前测试上下文的 a。随着测试的进行,还会更新 并委托给实现,这些实现检测实际 通过提供依赖项注入、管理事务等来测试执行。A 负责为给定测试加载 a 类。请参阅 javadoc 和 Spring 测试套件,以获取更多信息和各种实现的示例。TestContextManagerTestContextTestExecutionListenerSmartContextLoaderTestContextManagerTestContextManagerTestContextTestContextManagerTestContextTestExecutionListenerSmartContextLoaderApplicationContext

TestContext

TestContext封装运行测试的上下文(与 实际测试框架),并提供上下文管理和缓存支持 它负责的测试实例。如果需要,还会委托给 a 以加载 。TestContextSmartContextLoaderApplicationContext

TestContextManager

TestContextManager is the main entry point into the Spring TestContext Framework and is responsible for managing a single and signaling events to each registered at well-defined test execution points:TestContextTestExecutionListener

  • Prior to any “before class” or “before all” methods of a particular testing framework.

  • Test instance post-processing.

  • Prior to any “before” or “before each” methods of a particular testing framework.

  • Immediately before execution of the test method but after test setup.

  • Immediately after execution of the test method but before test tear down.

  • After any “after” or “after each” methods of a particular testing framework.

  • After any “after class” or “after all” methods of a particular testing framework.

TestExecutionListener

TestExecutionListener defines the API for reacting to test-execution events published by the with which the listener is registered. See TestExecutionListener Configuration.TestContextManager

Context Loaders

ContextLoader is a strategy interface for loading an for an integration test managed by the Spring TestContext Framework. You should implement instead of this interface to provide support for component classes, active bean definition profiles, test property sources, context hierarchies, and support.ApplicationContextSmartContextLoaderWebApplicationContext

SmartContextLoader is an extension of the interface that supersedes the original minimal SPI. Specifically, a can choose to process resource locations, component classes, or context initializers. Furthermore, a can set active bean definition profiles and test property sources in the context that it loads.ContextLoaderContextLoaderSmartContextLoaderSmartContextLoader

Spring provides the following implementations:

  • DelegatingSmartContextLoader: One of two default loaders, it delegates internally to an , a , or a , depending either on the configuration declared for the test class or on the presence of default locations or default configuration classes. Groovy support is enabled only if Groovy is on the classpath.AnnotationConfigContextLoaderGenericXmlContextLoaderGenericGroovyXmlContextLoader

  • WebDelegatingSmartContextLoader: One of two default loaders, it delegates internally to an , a , or a , depending either on the configuration declared for the test class or on the presence of default locations or default configuration classes. A web is used only if is present on the test class. Groovy support is enabled only if Groovy is on the classpath.AnnotationConfigWebContextLoaderGenericXmlWebContextLoaderGenericGroovyXmlWebContextLoaderContextLoader@WebAppConfiguration

  • AnnotationConfigContextLoader: Loads a standard from component classes.ApplicationContext

  • AnnotationConfigWebContextLoader: Loads a from component classes.WebApplicationContext

  • GenericGroovyXmlContextLoader: Loads a standard from resource locations that are either Groovy scripts or XML configuration files.ApplicationContext

  • GenericGroovyXmlWebContextLoader: Loads a from resource locations that are either Groovy scripts or XML configuration files.WebApplicationContext

  • GenericXmlContextLoader: Loads a standard from XML resource locations.ApplicationContext

  • GenericXmlWebContextLoader: Loads a from XML resource locations.WebApplicationContext

3.5.2. Bootstrapping the TestContext Framework

The default configuration for the internals of the Spring TestContext Framework is sufficient for all common use cases. However, there are times when a development team or third party framework would like to change the default , implement a custom or , augment the default sets of and implementations, and so on. For such low-level control over how the TestContext framework operates, Spring provides a bootstrapping strategy.ContextLoaderTestContextContextCacheContextCustomizerFactoryTestExecutionListener

TestContextBootstrapper defines the SPI for bootstrapping the TestContext framework. A is used by the to load the implementations for the current test and to build the that it manages. You can configure a custom bootstrapping strategy for a test class (or test class hierarchy) by using , either directly or as a meta-annotation. If a bootstrapper is not explicitly configured by using , either the or the is used, depending on the presence of .TestContextBootstrapperTestContextManagerTestExecutionListenerTestContext@BootstrapWith@BootstrapWithDefaultTestContextBootstrapperWebTestContextBootstrapper@WebAppConfiguration

Since the SPI is likely to change in the future (to accommodate new requirements), we strongly encourage implementers not to implement this interface directly but rather to extend or one of its concrete subclasses instead.TestContextBootstrapperAbstractTestContextBootstrapper

3.5.3. 配置TestExecutionListener

Spring 提供了以下已注册的实现 默认情况下,完全按以下顺序排列:TestExecutionListener

  • ServletTestExecutionListener:将 Servlet API 模拟配置为 .WebApplicationContext

  • DirtiesContextBeforeModesTestExecutionListener:处理“之前”模式的注释。@DirtiesContext

  • ApplicationEventsTestExecutionListener:提供对 ApplicationEvents 的支持。

  • DependencyInjectionTestExecutionListener:为测试提供依赖注入 实例。

  • DirtiesContextTestExecutionListener:处理 的注释 “之后”模式。@DirtiesContext

  • TransactionalTestExecutionListener:提供事务性测试执行 默认回滚语义。

  • SqlScriptsTestExecutionListener:运行使用注释配置的 SQL 脚本。@Sql

  • EventPublishingTestExecutionListener:将测试执行事件发布到测试(请参阅测试执行事件)。ApplicationContext

注册实现TestExecutionListener

您可以显式地为测试类注册实现,其 子类,以及使用注释的嵌套类。有关详细信息和示例,请参阅注释支持和 javadoc 以获取@TestExecutionListenersTestExecutionListener@TestExecutionListeners

切换到默认实现TestExecutionListener

如果您扩展了带注释的类,并且您需要 切换到使用默认的侦听器集,你可以用 以后。@TestExecutionListeners

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

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

具体来说,该模块在 其属性文件。第三方框架和开发人员 可以将自己的实现贡献到默认列表中 侦听器以同样的方式通过他们自己的属性 文件。spring-testTestExecutionListenerorg.springframework.test.context.TestExecutionListenerMETA-INF/spring.factoriesTestExecutionListenerMETA-INF/spring.factories

订购实现TestExecutionListener

当 TestContext 框架发现默认实现时 通过上述机制,实例化的监听器使用 Spring's ,它尊重 Spring 的接口和用于排序的注解。 Spring 提供的所有默认实现都使用 适当的值。因此,第三方框架和开发人员应确保 它们的默认实现按正确的顺序注册 通过实现或声明 .有关核心默认实现的方法,请参阅 javadoc,了解以下内容的详细信息 值分配给每个核心侦听器。TestExecutionListenerSpringFactoriesLoaderAnnotationAwareOrderComparatorOrdered@OrderAbstractTestExecutionListenerTestExecutionListenerOrderedTestExecutionListenerOrdered@OrdergetOrder()TestExecutionListener

合并实现TestExecutionListener

如果自定义是通过 注册的,则 未注册默认侦听器。在最常见的测试场景中,这实际上 强制开发人员手动声明除任何自定义侦听器之外的所有默认侦听器 听众。下面的清单演示了这种配置方式:TestExecutionListener@TestExecutionListeners

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

这种方法的挑战在于,它要求开发人员确切地知道 默认情况下注册了哪些侦听器。此外,默认侦听器集可以 从一个版本到另一个版本的更改 — 例如,WAS 在 Spring Framework 4.1 中引入,并在 Spring Framework 4.2 中引入。此外,像 Spring 这样的第三方框架 Boot 和 Spring Security 使用上述自动发现机制注册自己的默认实现。SqlScriptsTestExecutionListenerDirtiesContextBeforeModesTestExecutionListenerTestExecutionListener

为了避免必须知道并重新声明所有缺省侦听器,可以将 的属性设置为 。 指示本地声明的侦听器应与 默认侦听器。合并算法可确保从 列表,并且根据语义对合并的侦听器集进行排序 的,如排序 TestExecutionListener 实现中所述。 如果侦听器实现或注释了 ,它可以影响 它与默认值合并的位置。否则,本地声明的侦听器 在合并时附加到默认侦听器列表中。mergeMode@TestExecutionListenersMergeMode.MERGE_WITH_DEFAULTSMERGE_WITH_DEFAULTSAnnotationAwareOrderComparatorOrdered@Order

例如,如果上一个示例中的类 将其值(例如,)配置为小于 的顺序(恰好是 ),然后可以自动与 默认值,前面的示例可以 替换为以下内容:MyCustomTestExecutionListenerorder500ServletTestExecutionListener1000MyCustomTestExecutionListenerServletTestExecutionListener

爪哇岛
Kotlin
@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}

3.5.4. Application Events

Since Spring Framework 5.3.3, the TestContext framework provides support for recording application events published in the so that assertions can be performed against those events within tests. All events published during the execution of a single test are made available via the API which allows you to process the events as a .ApplicationContextApplicationEventsjava.util.Stream

To use in your tests, do the following.ApplicationEvents

  • Ensure that your test class is annotated or meta-annotated with @RecordApplicationEvents.

  • 确保已注册。但请注意, 默认注册,只需要 如果您具有自定义配置,则手动注册,其中不包括默认侦听器。ApplicationEventsTestExecutionListenerApplicationEventsTestExecutionListener@TestExecutionListeners

  • 在测试和生命周期方法(例如 JUnit Jupiter 中的 和 方法)中使用该类型的字段和实例。ApplicationEvents@AutowiredApplicationEvents@BeforeEach@AfterEach

    • 使用 SpringExtension for JUnit Jupiter 时,您可以声明一个方法 测试或生命周期方法中的类型参数作为替代项 添加到测试类中的字段。ApplicationEvents@Autowired

以下测试类使用 for JUnit、Jupiter 和 AssertJ 来断言应用程序事件的类型 在 Spring 管理的组件中调用方法时发布:SpringExtension

爪哇岛
Kotlin
@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 使用 API 计算已发布的事件数。ApplicationEventsOrderSubmitted

有关 API 的更多详细信息,请参阅 ApplicationEvents javadocApplicationEvents

3.5.5. 测试执行事件

Spring Framework 5.2 中引入的 实现自定义 .中的组件 test 可以监听 发布的以下事件,每个事件对应 API 中的一个方法。EventPublishingTestExecutionListenerTestExecutionListenerApplicationContextEventPublishingTestExecutionListenerTestExecutionListener

  • BeforeTestClassEvent

  • PrepareTestInstanceEvent

  • BeforeTestMethodEvent

  • BeforeTestExecutionEvent

  • AfterTestExecutionEvent

  • AfterTestMethodEvent

  • AfterTestClassEvent

这些事件可能出于各种原因而被使用,例如重置模拟 Bean 或跟踪 测试执行。使用测试执行事件而不是实现测试执行事件的一个优点 自定义是测试执行事件可以由任何 春豆在测试中注册,这样的豆子可能会受益 直接从依赖注入和 的其他功能中获取。在 相比之下,a 不是 .TestExecutionListenerApplicationContextApplicationContextTestExecutionListenerApplicationContext

默认注册;然而,它只是 如果已加载,则发布事件。这样可以防止不必要或过早加载。EventPublishingTestExecutionListenerApplicationContextApplicationContext

因此,在另一个 加载之前,不会发布 。例如,使用 注册的默认实现集 A 不会为使用 特定的测试,但发布 同一测试套件中使用相同测试的任何后续测试类,因为在后续测试时已经加载了上下文 类运行(只要上下文尚未从 VIA 或 max-size 逐出策略中删除)。BeforeTestClassEventApplicationContextTestExecutionListenerTestExecutionListenerBeforeTestClassEventApplicationContextBeforeTestClassEventApplicationContextContextCache@DirtiesContext

如果您希望确保始终为每个测试发布 类,您需要在回调中注册一个加载 的,并且必须在 .BeforeTestClassEventTestExecutionListenerApplicationContextbeforeTestClassTestExecutionListenerEventPublishingTestExecutionListener

同样,if 用于从 上下文缓存 在给定测试类中的最后一个测试方法之后,将不会为该测试类发布。@DirtiesContextApplicationContextAfterTestClassEvent

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

  • @BeforeTestClass

  • @PrepareTestInstance

  • @BeforeTestMethod

  • @BeforeTestExecution

  • @AfterTestExecution

  • @AfterTestMethod

  • @AfterTestClass

异常处理

默认情况下,如果测试执行事件侦听器在使用 事件,该异常将传播到正在使用的底层测试框架(例如 JUnit 或 TestNG)。例如,如果 a 的消耗导致 如果出现异常,相应的测试方法将因异常而失败。在 相反,如果异步测试执行事件侦听器抛出异常,则 异常不会传播到基础测试框架。有关更多详细信息 异步异常处理,请查阅类级 javadoc 了解。BeforeTestMethodEvent@EventListener

异步侦听器

如果希望特定的测试执行事件侦听器异步处理事件, 您可以使用 Spring 的常规@Async支持。有关更多详细信息,请查阅类级 javadoc 以获取 。@EventListener

3.5.6. 上下文管理

每个实例都为测试实例提供上下文管理和缓存支持 它对此负责。测试实例不会自动接收对 配置。但是,如果测试类实现该接口,则提供对 到测试实例。请注意,并实现,因此, 提供对 自动.TestContextApplicationContextApplicationContextAwareApplicationContextAbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTestsApplicationContextAwareApplicationContext

@Autowired ApplicationContext

作为实现接口的替代方法,您可以注入 测试类的应用程序上下文,通过 字段或 setter 方法,如以下示例所示:ApplicationContextAware@Autowired

爪哇岛
Kotlin
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    ApplicationContext applicationContext;

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

同样,如果测试配置为加载 ,则可以注入 将 Web 应用程序上下文添加到测试中,如下所示:WebApplicationContext

爪哇岛
Kotlin
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    WebApplicationContext wac;

    // class body...
}
1 配置 .WebApplicationContext
2 注入 .WebApplicationContext

依赖关系注入由 提供,默认配置为 (请参阅测试夹具的依赖注入)。@AutowiredDependencyInjectionTestExecutionListener

使用 TestContext 框架的测试类不需要扩展任何特定的 类或实现特定接口以配置其应用程序上下文。相反 配置是通过在 班级级别。如果测试类未显式声明应用程序上下文资源 位置或组件类,则配置决定了如何加载 来自默认位置或默认配置类的上下文。除了上下文 资源位置和组件类,也可以配置应用程序上下文 通过应用程序上下文初始值设定项。@ContextConfigurationContextLoader

以下各节解释了如何使用 Spring 的注解来 使用 XML 配置文件、Groovy 脚本、 组件类(通常为类)或上下文初始值设定项。 或者,您可以实现和配置自己的自定义 高级用例。@ContextConfigurationApplicationContext@ConfigurationSmartContextLoader

使用 XML 资源进行上下文配置

若要使用 XML 配置文件加载测试,请注释 测试类,并使用 一个数组,其中包含 XML 配置元数据的资源位置。普通或 相对路径(例如,)被视为类路径资源,即 相对于定义测试类的包。以斜杠开头的路径 被视为绝对类路径位置(例如,)。一个 表示资源 URL(即以 、 、 等为前缀的路径)的路径按原样使用。ApplicationContext@ContextConfigurationlocationscontext.xml/org/example/config.xmlclasspath:file:http:

爪哇岛
Kotlin
@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 文件列表。

@ContextConfiguration支持通过 标准 Java 属性。因此,如果您不需要声明额外的 中的属性,可以省略属性名称的声明,并使用速记格式声明资源位置 以下示例演示了:locationsvalue@ContextConfigurationlocations

爪哇岛
Kotlin
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
1 在不使用属性的情况下指定 XML 文件。location

如果从注释中省略 和 属性,则 TestContext 框架会尝试检测默认值 XML 资源位置。具体来说,并根据测试的名称检测默认位置 类。如果您的类被命名为 ,则加载您的 应用程序上下文。以下 示例演示如何执行此操作:locationsvalue@ContextConfigurationGenericXmlContextLoaderGenericXmlWebContextLoadercom.example.MyTestGenericXmlContextLoader"classpath:com/example/MyTest-context.xml"

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

要使用使用 Groovy Bean 定义 DSL 的 Groovy 脚本为测试加载 测试类,并使用包含 Groovy 脚本资源位置的数组配置 or 属性。资源 Groovy 脚本的查找语义与 XML 配置文件的查找语义相同。ApplicationContext@ContextConfigurationlocationsvalue

启用 Groovy 脚本支持
支持使用 Groovy 脚本在 Spring 中加载 如果 Groovy 位于类路径上,则会自动启用 TestContext Framework。ApplicationContext

以下示例演示如何指定 Groovy 配置文件:

爪哇岛
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...
}

如果从注解中省略 和 属性,TestContext 框架会尝试检测默认的 Groovy 脚本。 具体而言,并根据测试类的名称检测默认位置。如果类已命名,则 Groovy 上下文加载器将从 加载应用程序上下文。以下示例演示如何使用 默认值:locationsvalue@ContextConfigurationGenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoadercom.example.MyTest"classpath:com/example/MyTestContext.groovy"

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

您可以使用以下命令同时声明 XML 配置文件和 Groovy 脚本 的 or 属性。如果路径指向 配置的资源位置以 结尾,它是使用 .否则,将使用 .locationsvalue@ContextConfiguration.xmlXmlBeanDefinitionReaderGroovyBeanDefinitionReader

下面的清单显示了如何在集成测试中将两者结合起来:

爪哇岛
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...
}
使用组件类进行上下文配置

要使用组件类为测试加载(请参阅基于 Java 的容器配置),可以对测试进行注释 类并使用数组配置属性 包含对组件类的引用。以下示例演示如何执行此操作:ApplicationContext@ContextConfigurationclasses

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

术语“组件类”可以指以下任何一种:

  • 用 批注的类。@Configuration

  • 组件(即用 、、 或其他构造型批注批注的类)。@Component@Service@Repository

  • 一个符合 JSR-330 的类,带有注释。javax.inject

  • 任何包含 -methods 的类。@Bean

  • 打算注册为 Spring 组件的任何其他类(即 Spring bean 在 中,可能利用自动自动布线 的单个构造函数,而不使用 Spring 注解。ApplicationContext

有关更多信息,请参阅 @Configuration@Bean 的 javadoc 关于组件类的配置和语义,要特别注意 到精简模式的讨论。@Bean

如果从注释中省略该属性,则 TestContext 框架尝试检测默认配置类是否存在。 具体来说,并检测满足以下要求的测试类的所有嵌套类 配置类实现,如 @Configuration javadoc 中指定。 请注意,配置类的名称是任意的。此外,测试类可以 如果需要,包含多个嵌套配置类。在以下内容中 示例中,该类声明了一个嵌套的配置类 named 自动用于加载测试的 类:classes@ContextConfigurationAnnotationConfigContextLoaderAnnotationConfigWebContextLoaderstaticstaticOrderServiceTeststaticConfigApplicationContext

爪哇岛
Kotlin
@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
混合 XML、Groovy 脚本和组件类

有时可能需要混合 XML 配置文件、Groovy 脚本和 组件类(通常是类)来配置测试。例如,如果在 production,您可以决定要使用类来配置 用于测试的特定 Spring 管理组件,反之亦然。@ConfigurationApplicationContext@Configuration

此外,一些第三方框架(如 Spring Boot)提供了一流的 支持从不同类型的资源加载 (例如,XML 配置文件、Groovy 脚本和类)。从历史上看,Spring Framework 不支持这一点 标准部署。因此,大多数实现 Spring Framework 在模块中仅支持一种资源类型 对于每个测试上下文。但是,这并不意味着您不能同时使用两者。一 一般规则的例外是 和 同时支持 XML 配置文件和 Groovy 脚本。此外,第三方框架可以选择支持 声明 both 和 through , 和 TestContext 框架中的标准测试支持,您有以下选项。ApplicationContext@ConfigurationSmartContextLoaderspring-testGenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoaderlocationsclasses@ContextConfiguration

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

使用上下文初始值设定项进行上下文配置

若要使用上下文初始值设定项为测试配置,请执行以下操作: 使用一个数组对测试类进行注释,并使用一个数组配置该属性,该数组包含对实现 的类的引用。然后,声明的上下文初始值设定项用于 初始化为测试加载的。请注意, 每个声明的初始值设定项支持的具体类型 必须与正在使用的创建的类型兼容(通常为 )。此外, 调用初始值设定项的顺序取决于它们是实现 Spring 的接口,还是使用 Spring 的注解或标准注解进行注解。以下示例演示如何使用初始值设定项:ApplicationContext@ContextConfigurationinitializersApplicationContextInitializerConfigurableApplicationContextConfigurableApplicationContextApplicationContextSmartContextLoaderGenericApplicationContextOrdered@Order@Priority

爪哇岛
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 脚本或 组件类,而是只声明类,然后负责注册 bean 在上下文中 — 例如,通过以编程方式从 XML 加载 Bean 定义 文件或配置类。以下示例演示如何执行此操作:@ContextConfigurationApplicationContextInitializer

爪哇岛
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支持布尔值和属性,这些属性表示资源位置或组件类和上下文 应继承由超类声明的初始值设定项。两者的默认值 flags 是 。这意味着测试类继承资源位置或 组件类以及由任何超类声明的上下文初始值设定项。 具体而言,将追加测试类的资源位置或组件类 添加到资源位置或超类声明的带注释的类的列表。 同样,给定测试类的初始值设定项将添加到初始值设定项集中 由测试超类定义。因此,子类可以选择扩展资源 位置、组件类或上下文初始值设定项。inheritLocationsinheritInitializerstrue

如果 中的 or 属性设置为 ,则资源位置或组件类和上下文 初始值设定项,分别用于测试类 Shadow 并有效地替换 由超类定义的配置。inheritLocationsinheritInitializers@ContextConfigurationfalse

从 Spring Framework 5.3 开始,测试配置也可以继承自 enclosed 类。有关详细信息@Nested请参阅测试类配置

在下一个使用 XML 资源位置的示例中,按该顺序从 和 加载 for。 因此,中定义的 Bean 可以覆盖(即替换)那些 中定义。下面的示例演示一个类如何扩展 另一个,同时使用自己的配置文件和超类的配置文件:ApplicationContextExtendedTestbase-config.xmlextended-config.xmlextended-config.xmlbase-config.xml

爪哇岛
Kotlin
@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 子类中定义的配置文件。

同样,在下一个使用组件类的示例中,for 是从 和 类加载的,其中 次序。因此,中定义的 Bean 可以覆盖(即替换) 中定义的那些。下面的示例演示一个类如何扩展 另一个,同时使用自己的配置类和超类的配置类:ApplicationContextExtendedTestBaseConfigExtendedConfigExtendedConfigBaseConfig

爪哇岛
Kotlin
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
    // class body...
}

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

在下一个使用上下文初始值设定项的示例中,通过使用 和 来初始化 for。注意 但是,调用初始值设定项的顺序取决于它们是否 实现 Spring 的接口或使用 Spring 的注解进行注解 或标准注释。下面的示例演示一个类如何 扩展另一个并同时使用自己的初始值设定项和超类的初始值设定项:ApplicationContextExtendedTestBaseInitializerExtendedInitializerOrdered@Order@Priority

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

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

Spring Framework 对环境和配置文件的概念提供了一流的支持 (又名“bean 定义配置文件”),并且可以将集成测试配置为激活 用于各种测试场景的特定 Bean 定义配置文件。这是通过以下方式实现的 使用注解对测试类进行注解,并提供 加载测试时应激活的配置文件。@ActiveProfilesApplicationContext

您可以与SPI的任何实现一起使用,但不支持旧SPI的实现。@ActiveProfilesSmartContextLoader@ActiveProfilesContextLoader

考虑两个 XML 配置和类的示例:@Configuration

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

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

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

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

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

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

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

</beans>
爪哇岛
Kotlin
@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
    }
}

运行时,它会从类路径根目录中的配置文件加载。如果检查 ,您可以看到 Bean 依赖于 Bean。但是,未定义为顶级 Bean。相反,被定义了三次:在配置文件中,在配置文件中, 和在配置文件中。TransferServiceTestApplicationContextapp-config.xmlapp-config.xmlaccountRepositorydataSourcedataSourcedataSourceproductiondevdefault

通过注释 ,我们指示 Spring TestContext Framework 加载活动配置文件设置为 .因此,将创建一个嵌入式数据库并填充测试数据,并且 Bean 与开发相关的参考。 这可能是我们在集成测试中想要的。TransferServiceTest@ActiveProfiles("dev")ApplicationContext{"dev"}accountRepositoryDataSource

有时将 Bean 分配给配置文件很有用。缺省值内的 Bean 仅当没有专门激活其他配置文件时,才会包含配置文件。你可以使用 这是为了定义要在应用程序的缺省状态中使用的“回退”bean。为 例如,您可以显式提供 和 配置文件的数据源, 但是,当内存中数据源都不处于活动状态时,将这些数据源定义为默认值。defaultdevproduction

以下代码清单演示了如何实现相同的配置和 使用类而不是 XML 进行集成测试:@Configuration

爪哇岛
Kotlin
@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("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("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
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
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

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

在此变体中,我们将 XML 配置拆分为四个独立的类:@Configuration

  • TransferServiceConfig:通过使用 获取依赖注入。dataSource@Autowired

  • StandaloneDataConfig:为适合 开发人员测试。dataSource

  • JndiDataConfig:定义从生产中的 JNDI 检索到的 环境。dataSource

  • DefaultDataConfig:为默认嵌入式数据库定义一个,以防万一 配置文件处于活动状态。dataSource

与基于 XML 的配置示例一样,我们仍然使用 进行注释,但这次我们通过 使用注释。测试类本身的主体仍然存在 完全没有变化。TransferServiceTest@ActiveProfiles("dev")@ContextConfiguration

通常情况下,在多个测试类中使用一组配置文件 在给定项目中。因此,为了避免重复声明注释,可以在基类和子类上声明一次 自动从基类继承配置。在 以下示例,声明(以及其他注释) 已移至抽象超类:@ActiveProfiles@ActiveProfiles@ActiveProfiles@ActiveProfilesAbstractIntegrationTest

从 Spring Framework 5.3 开始,测试配置也可以继承自 enclosed 类。有关详细信息@Nested请参阅测试类配置
爪哇岛
Kotlin
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
爪哇岛
Kotlin
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    TransferService transferService;

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

@ActiveProfiles还支持可用于 禁用活动配置文件的继承,如以下示例所示:inheritProfiles

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

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

  • 当前操作系统。

  • 测试是否在持续集成生成服务器上运行。

  • 存在某些环境变量。

  • 自定义类级批注的存在。

  • 其他问题。

要以编程方式解析活动 Bean 定义概要文件,可以实现 自定义并使用 的属性进行注册。有关详细信息,请参阅相应的 javadoc。 以下示例演示如何实现和注册自定义:ActiveProfilesResolverresolver@ActiveProfilesOperatingSystemActiveProfilesResolver

爪哇岛
Kotlin
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver.class,
        inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
爪哇岛
Kotlin
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};
    }
}
使用测试属性源进行上下文配置

Spring Framework 对具有 属性源的层次结构,并且可以使用特定于测试的集成测试进行配置 属性源。与类上使用的注释相反,您可以在测试中声明注释 类来声明测试属性文件或内联属性的资源位置。 这些测试属性源将添加到 for the loaded for the loaded for the annotated integration test 的集合中。@PropertySource@Configuration@TestPropertySourcePropertySourcesEnvironmentApplicationContext

您可以与SPI的任何实现一起使用,但不支持旧SPI的实现。@TestPropertySourceSmartContextLoader@TestPropertySourceContextLoader

获得对合并测试属性源值的访问权限的实现 通过 中的 和 方法。SmartContextLoadergetPropertySourceLocations()getPropertySourceProperties()MergedContextConfiguration

声明测试属性源

可以使用 的 or 属性来配置测试属性文件。locationsvalue@TestPropertySource

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

每条路径都被解释为 Spring 。普通路径(例如,)被视为相对于包的类路径资源 其中定义了测试类。以斜杠开头的路径被视为 绝对类路径资源(例如:)。一条路径 引用 URL(例如,以 、 或 为前缀的路径)是 使用指定的资源协议加载。不允许使用资源位置通配符(例如 ):每个位置的计算结果必须恰好为一个或资源。Resource"test.properties""/org/example/test.xml"classpath:file:http:*/.properties.properties.xml

以下示例使用测试属性文件:

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

您可以使用 的属性以键值对的形式配置内联属性,如下例所示。都 键值对将作为具有最高优先级的单个测试添加到封闭中。properties@TestPropertySourceEnvironmentPropertySource

键值对支持的语法与为 Java 属性文件:

  • key=value

  • key:value

  • key value

下面的示例设置两个内联属性:

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

从 Spring Framework 5.2 开始,可以用作可重复的注解。 这意味着您可以在单个 测试类,后面的注解中的 和会覆盖以前注解中的注释。@TestPropertySource@TestPropertySourcelocationsproperties@TestPropertySource@TestPropertySource

此外,您可以在一个测试类上声明多个组合注释,每个注释都是 使用 进行元注释,所有这些声明都将有助于您的测试属性源。@TestPropertySource@TestPropertySource

直接呈现的注释始终优先于 元呈现注释。换言之,and from 一个直接存在的注解将覆盖 and from 一个用作 元注释。@TestPropertySource@TestPropertySourcelocationsproperties@TestPropertySourcelocationsproperties@TestPropertySource

默认属性文件检测

If 声明为空注解(即,没有显式 或属性的值),尝试检测 相对于声明注释的类的缺省属性文件。例如 如果带批注的测试类是 ,则相应的默认属性 文件是 。如果无法检测到默认值,则抛出 。@TestPropertySourcelocationspropertiescom.example.MyTestclasspath:com/example/MyTest.propertiesIllegalStateException

优先

测试属性的优先级高于操作系统的 环境、Java 系统属性或应用程序添加的属性源 使用或以编程方式以声明方式。因此,测试属性可以 用于有选择地覆盖从系统和应用程序属性加载的属性 来源。此外,内联属性的优先级高于加载的属性 从资源位置。但请注意,通过@DynamicPropertySource注册的属性具有 优先级高于通过 加载的优先级。@PropertySource@TestPropertySource

在下一个示例中,和属性以及 中定义的任何属性将覆盖系统中定义的任何同名属性 和应用程序属性源。此外,如果文件定义 和属性的条目,这些属性被内联的 使用该特性声明的属性。以下示例演示如何 要同时指定文件和内联属性,请执行以下操作:timezoneport"/test.properties""/test.properties"timezoneportproperties

爪哇岛
Kotlin
@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // class body...
}
继承和重写测试属性源

@TestPropertySource支持布尔值和属性,这些属性表示属性文件和内联的资源位置 应继承超类声明的属性。两个标志的默认值 是。这意味着测试类继承位置和内联属性 由任何超类声明。具体而言,位置和内联属性 测试类将追加到超类声明的位置和内联属性中。 因此,子类可以选择扩展位置和内联属性。注意 稍后出现的属性会遮蔽(即覆盖)同名属性 出现较早。此外,上述优先规则适用于继承的 测试属性源。inheritLocationsinheritPropertiestrue

如果 中的 or 属性是 设置为 ,分别为测试类的位置或内联属性 影子,并有效地替换超类定义的配置。inheritLocationsinheritProperties@TestPropertySourcefalse

从 Spring Framework 5.3 开始,测试配置也可以继承自 enclosed 类。有关详细信息@Nested请参阅测试类配置

在下一个示例中,仅使用文件作为测试属性源来加载 for。相反,通过使用 和 文件作为测试属性源位置来加载 for。以下示例演示如何定义 子类及其超类中的属性(通过使用文件):ApplicationContextBaseTestbase.propertiesApplicationContextExtendedTestbase.propertiesextended.propertiesproperties

爪哇岛
Kotlin
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}

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

在下一个示例中,仅使用 inlined 属性。相比之下,for 是 使用 inlined 和 properties 加载。以下示例演示如何 使用内联属性定义子类及其超类中的属性:ApplicationContextBaseTestkey1ApplicationContextExtendedTestkey1key2

爪哇岛
Kotlin
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
    // ...
}

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

从 Spring Framework 5.2.5 开始,TestContext 框架通过注解提供对动态属性的支持。此注解可用于 需要将具有动态值的属性添加到 for for 集成测试。@DynamicPropertySourcePropertySourcesEnvironmentApplicationContext

注释及其支持的基础结构是 最初设计为允许基于 Testcontainers 的测试中的属性轻松公开给 Spring 集成测试。但是,此功能也可以与任何形式的 其生命周期维护在测试的 .@DynamicPropertySourceApplicationContext

与在类级别应用的 @TestPropertySource 注释相反,必须应用 更改为接受单个参数的方法,该参数是 用于向 .值是动态的,通过 a 仅在解析属性时调用。通常,方法 引用用于提供值,如以下示例所示,该示例使用 Testcontainers 项目来管理 Spring 之外的 Redis 容器。创建托管 Redis 容器的 IP 地址和端口 可通过 和 属性用于测试中的组件。这些属性可以通过 Spring 的抽象访问,也可以直接注入到 Spring 管理的组件中——例如,分别是 via 和 。@DynamicPropertySourcestaticDynamicPropertyRegistryEnvironmentSupplierApplicationContextApplicationContextredis.hostredis.portEnvironment@Value("${redis.host}")@Value("${redis.port}")

如果在基类中使用并发现在子类中进行测试 失败,因为子类之间的动态属性会发生变化,您可能需要注释 您的基类,@DirtiesContext 确保每个子类都具有自己的正确动态 性能。@DynamicPropertySourceApplicationContext

爪哇岛
Kotlin
@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 ...

}
优先

动态属性的优先级高于从 加载的属性。 操作系统的环境、Java 系统属性或添加的属性源 应用程序以声明方式使用 或 以编程方式。因此 动态属性可用于有选择地重写通过 、 系统属性源和应用程序属性源加载的属性。@TestPropertySource@PropertySource@TestPropertySource

加载一个WebApplicationContext

指示 TestContext 框架加载 标准,您可以使用 注释相应的测试类。WebApplicationContextApplicationContext@WebAppConfiguration

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

请注意,Spring 对实现的测试支持是相当的 支持标准实现。当使用 进行测试时,您可以自由声明 XML 配置文件、Groovy 脚本、 或使用 .您也可以免费使用 任何其他测试注释,例如 、 、 、 等。WebApplicationContextApplicationContextWebApplicationContext@Configuration@ContextConfiguration@ActiveProfiles@TestExecutionListeners@Sql@Rollback

本节中的其余示例显示了 加载 .下面的示例演示 TestContext 框架对约定优先于配置的支持:WebApplicationContext

爪哇岛
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 {
    //...
}

如果在未指定资源的情况下对测试类进行注释 基本路径,则资源路径实际上默认为 。同样地 如果声明时没有指定 resource , component 或 context ,Spring 会尝试检测 使用约定进行配置(即,在同一包中 作为类或静态嵌套类)。@WebAppConfigurationfile:src/main/webapp@ContextConfigurationlocationsclassesinitializersWacTests-context.xmlWacTests@Configuration

下面的示例演示如何使用 显式声明资源基路径,并使用 XML 声明 XML 资源位置:@WebAppConfiguration@ContextConfiguration

爪哇岛
Kotlin
@ExtendWith(SpringExtension.class)

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

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

这里需要注意的重要一点是,这两个路径的语义不同 附注。默认情况下,资源路径是基于文件系统的, 而资源位置是基于类路径的。@WebAppConfiguration@ContextConfiguration

以下示例显示,我们可以覆盖两者的默认资源语义 通过指定 Spring 资源前缀进行注解:

爪哇岛
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 {
    //...
}

将此示例中的注释与上一个示例进行对比。

使用 Web 模拟

为了提供全面的 Web 测试支持,TestContext 框架默认启用了 WebContext 框架。当针对 进行测试时,此 TestExecutionListener 使用 Spring Web 的 before 设置默认线程本地状态 每个测试方法并创建一个 、 和 a 基于配置了 的基本资源路径。 也保证了 and 可以注入到测试实例中, 并且,一旦测试完成,它就会清理线程本地状态。ServletTestExecutionListenerWebApplicationContextRequestContextHolderMockHttpServletRequestMockHttpServletResponseServletWebRequest@WebAppConfigurationServletTestExecutionListenerMockHttpServletResponseServletWebRequest

一旦你加载了测试,你可能会发现你 需要与网络模拟进行交互 - 例如,设置您的测试夹具或 调用 Web 组件后执行断言。以下示例显示了 模拟可以自动连接到测试实例中。请注意,和都缓存在测试套件中,而其他模拟则缓存在测试套件中。 每个测试方法由 .WebApplicationContextWebApplicationContextMockServletContextServletTestExecutionListener

爪哇岛
Kotlin
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}
上下文缓存

一旦 TestContext 框架加载一个(或 ) 对于测试,该上下文将被缓存并重用于声明 在同一测试套件中具有相同的唯一上下文配置。了解如何缓存 工作,了解“唯一”和“测试套件”的含义很重要。ApplicationContextWebApplicationContext

可以通过配置组合进行唯一标识 用于加载它的参数。因此,配置的独特组合 parameters 用于生成一个键,在该键下缓存上下文。The TestContext 框架使用以下配置参数来构建上下文缓存密钥:ApplicationContext

  • locations(从@ContextConfiguration)

  • classes(从@ContextConfiguration)

  • contextInitializerClasses(从@ContextConfiguration)

  • contextCustomizers(from ) – 这包括 Spring Boot 的方法和各种功能 测试支持,例如 和 。ContextCustomizerFactory@DynamicPropertySource@MockBean@SpyBean

  • contextLoader(从@ContextConfiguration)

  • parent(从@ContextHierarchy)

  • activeProfiles(从@ActiveProfiles)

  • propertySourceLocations(从@TestPropertySource)

  • propertySourceProperties(从@TestPropertySource)

  • resourceBasePath(从@WebAppConfiguration)

例如,如果为 的 (或 ) 属性指定 ,则 TestContext 框架 加载相应的内容并将其存储在上下文缓存中 在仅基于这些位置的密钥下。因此,if 还为其位置定义(显式或 隐式通过继承)但未定义,不同的,不同的活动配置文件,不同的上下文初始值设定项,不同的 测试属性源或不同的父上下文,则两个测试类共享相同的属性。这意味着加载应用程序的设置成本 上下文只发生一次(每个测试套件),后续测试执行很多 更快。TestClassA{"app-config.xml", "test-config.xml"}locationsvalue@ContextConfigurationApplicationContextstaticTestClassB{"app-config.xml", "test-config.xml"}@WebAppConfigurationContextLoaderApplicationContext

测试套件和分叉进程

Spring TestContext 框架将应用程序上下文存储在静态缓存中。这 意味着上下文实际上存储在变量中。换言之,如果 测试在单独的进程中运行,静态缓存在每次测试之间被清除 执行,这有效地禁用了缓存机制。static

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

上下文缓存的大小受默认最大大小 32 的限制。每当 达到最大大小时,使用最近最少使用的 (LRU) 逐出策略进行逐出,并且 关闭陈旧的上下文。您可以从命令行或构建中配置最大大小 脚本,方法是设置名为 的 JVM 系统属性。作为 或者,您可以通过 SpringProperties 机制设置相同的属性。spring.test.context.cache.maxSize

由于在给定的测试套件中加载了大量应用程序上下文可以 导致套件需要不必要的长时间才能运行,这通常有利于 确切地知道已经加载和缓存了多少个上下文。查看统计信息 底层上下文缓存中,可以将日志记录类别的日志级别设置为 。org.springframework.test.context.cacheDEBUG

在极少数情况下,测试会损坏应用程序上下文并需要重新加载 (例如,通过修改 Bean 定义或应用程序对象的状态),您可以 可以注释你的测试类或测试方法(参见 Spring Testing 中的讨论 注释)。这指示 Spring 从缓存中删除上下文并重新构建 在运行需要相同应用程序的下一个测试之前的应用程序上下文 上下文。请注意,默认情况下启用的 和 提供对注释的支持。@DirtiesContext@DirtiesContext@DirtiesContextDirtiesContextBeforeModesTestExecutionListenerDirtiesContextTestExecutionListener

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

当您需要调试使用 Spring TestContext Framework 执行的测试时,它可以是 用于分析控制台输出(即输出到 和 流)。某些构建工具和 IDE 能够将控制台输出与给定的 测试;但是,某些控制台输出无法轻松与给定测试相关联。SYSOUTSYSERR

关于由 Spring Framework 本身或组件触发的控制台日志记录 注册,了解 Spring TestContext 框架在 测试套件。ApplicationContextApplicationContext

for 测试通常在测试实例出现时加载 类正在准备中 — 例如,对测试实例的字段执行依赖注入。这意味着在 的初始化通常不能与 个别测试方法。但是,如果上下文在紧接 执行测试方法 根据@DirtiesContext语义,将在执行 测试方法。在后一种情况下,IDE 或生成工具可能会关联 使用单个测试方法进行控制台日志记录。ApplicationContext@AutowiredApplicationContext

可以通过以下情况之一关闭测试。ApplicationContext

  • 上下文根据语义关闭。@DirtiesContext

  • 上下文已关闭,因为它已自动从缓存中逐出 根据 LRU 驱逐政策。

  • 当测试套件的 JVM 时,通过 JVM 关闭钩子关闭上下文 终止。

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

当通过 JVM 关闭钩子关闭 Spring 时,会执行回调 在关闭阶段,在名为 的线程上执行。所以 如果您希望禁用关闭时触发的控制台日志记录 通过 JVM shutdown 钩子,您可以在日志记录中注册自定义过滤器 允许您忽略该线程启动的任何日志记录的框架。ApplicationContextSpringContextShutdownHookApplicationContext

上下文层次结构

当编写依赖于加载的 Spring 的集成测试时,它是 通常足以针对单个上下文进行测试。但是,有时确实如此 对实例层次结构进行测试是有益的,甚至是必要的。例如,如果您正在开发 Spring MVC Web 应用程序,您通常 有一个由 Spring 加载的根和一个 由 Spring 的 .这会导致 父子上下文层次结构,其中共享组件和基础结构配置 在根上下文中声明,并在子上下文中由特定于 Web 的使用 组件。另一个用例可以在 Spring Batch 应用程序中找到,您经常在 具有为共享批处理基础结构提供配置的父上下文和 用于配置特定批处理作业的子上下文。ApplicationContextApplicationContextWebApplicationContextContextLoaderListenerWebApplicationContextDispatcherServlet

您可以通过声明上下文来编写使用上下文层次结构的集成测试 带有注释的配置,可以在单个测试类上 或在测试类层次结构中。如果在多个类上声明上下文层次结构 在测试类层次结构中,还可以合并或覆盖上下文配置 用于上下文层次结构中的特定命名级别。合并 层次结构中给定的级别,即配置资源类型(即 XML 配置 文件或组件类)必须一致。否则,这是完全可以接受的 在上下文层次结构中使用不同的资源类型配置不同的级别。@ContextHierarchy

本节中其余基于 JUnit Jupiter 的示例显示了通用配置 需要使用上下文层次结构的集成测试方案。

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

ControllerIntegrationTests表示 Spring MVC Web 应用程序通过声明由两个级别组成的上下文层次结构, 一个用于根(使用该类加载),一个用于调度程序 Servlet(使用该类加载)。自动连接到测试实例的是子上下文(即 层次结构中的最低上下文)。以下列表显示了此配置方案:WebApplicationContextTestAppConfig@ConfigurationWebApplicationContextWebConfig@ConfigurationWebApplicationContext

爪哇岛
Kotlin
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

    @Autowired
    WebApplicationContext wac;

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

此示例中的测试类在测试类中定义上下文层次结构 等级制度。 声明 Spring 驱动的 Web 应用程序中根的配置。但是请注意,这并没有声明 .因此,子类可以选择性地参与上下文层次结构或遵循 的标准语义。 并通过以下方式扩展和定义上下文层次结构 用。结果是加载了三个应用程序上下文(一个 对于每个声明,并加载应用程序上下文 基于中的配置,设置为每个 为具体子类加载的上下文。下面的清单显示了这一点 配置场景:AbstractWebTestsWebApplicationContextAbstractWebTests@ContextHierarchyAbstractWebTests@ContextConfigurationSoapWebServiceTestsRestWebServiceTestsAbstractWebTests@ContextHierarchy@ContextConfigurationAbstractWebTests

爪哇岛
Kotlin
@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 {}
具有合并上下文层次结构配置的类层次结构

此示例中的类显示如何使用命名层次结构级别来合并 上下文层次结构中特定级别的配置。 定义两个级别 在层次结构中,和 . 扩展和指示 Spring TestContext Framework 通过确保 中的属性中声明的名称都是 .结果是三个应用程序上下文 加载:一个用于 ,一个用于 ,一个用于 。与前面的示例一样, 应用程序上下文加载自 设置为父上下文 从 和 加载的上下文。 以下列表显示了此配置方案:BaseTestsparentchildExtendedTestsBaseTestschildname@ContextConfigurationchild/app-config.xml/user-config.xml{"/user-config.xml", "/order-config.xml"}/app-config.xml/user-config.xml{"/user-config.xml", "/order-config.xml"}

爪哇岛
Kotlin
@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 {}
具有覆盖上下文层次结构配置的类层次结构

与前面的示例相比,此示例演示了如何覆盖 通过将标志设置为 来配置上下文层次结构中的给定命名级别。因此, 的应用程序上下文 仅从 和 加载 将其父级设置为从 加载的上下文。以下列表 显示了以下配置方案:inheritLocations@ContextConfigurationfalseExtendedTests/test-user-config.xml/app-config.xml

爪哇岛
Kotlin
@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 {}
在上下文层次结构中弄脏上下文
如果在测试中使用,其上下文配置为 上下文层次结构,可以使用该标志来控制上下文缓存的方式 被清除。有关详细信息,请参阅 Spring Testing Annotations 中的讨论和 javadoc @DirtiesContext@DirtiesContexthierarchyMode@DirtiesContext

3.5.7. 测试夹具的依赖注入

当您使用 (由 default),测试实例的依赖项是从 您配置了应用程序上下文或与之相关的应用程序上下文 附注。您可以使用 setter 注入、场注入或两者兼而有之,具体取决于 选择哪些注释,以及是否将它们放在 setter 方法或字段上。 如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入 (参见 Dependency Injection with SpringExtension)。为了与 Spring 基于注解的 注入支持,您还可以使用 Spring 的注解或 JSR-330 的注解进行字段和 setter 注入。DependencyInjectionTestExecutionListener@ContextConfiguration@Autowired@Inject

对于 JUnit Jupiter 以外的测试框架,TestContext 框架不会 参与测试类的实例化。因此,对构造函数使用 or 对测试类没有影响。@Autowired@Inject
尽管在生产代码中不鼓励字段注入,但字段注入是 实际上在测试代码中很自然。造成这种差异的理由是,您将 永远不要直接实例化你的测试类。因此,没有必要能够 在测试类上调用构造函数或 setter 方法。public

因为用于执行自动接线 type,如果您有多个相同类型的 Bean 定义,则不能依赖此定义 针对这些特定 bean 的方法。在这种情况下,您可以在 结合。您也可以选择与 结合使用。或者,如果您的测试类可以访问其 ,则 可以通过使用(例如)调用 来执行显式查找。@Autowired@Autowired@Qualifier@Inject@NamedApplicationContextapplicationContext.getBean("titleRepository", TitleRepository.class)

如果您不希望将依赖注入应用于测试实例,请不要注释 字段或 setter 方法。或者,您可以禁用 通过显式配置类来注入依赖项,并从侦听器列表中省略。@Autowired@Inject@TestExecutionListenersDependencyInjectionTestExecutionListener.class

考虑测试类的方案,如“目标”部分所述。接下来的两个代码清单演示了 使用 on 字段和 setter 方法。应用程序上下文配置 显示在所有示例代码列表之后。HibernateTitleRepository@Autowired

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

以下示例调用静态断言方法,例如 但不要在调用前面加上 .在这种情况下,假设该方法 通过未显示在 例。assertNotNull()Assertionsimport static

第一个代码清单显示了测试类的基于 JUnit Jupiter 的实现,该实现 现场注入的用途:@Autowired

爪哇岛
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
    HibernateTitleRepository titleRepository;

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

或者,可以将类配置为用于 setter 注入,如 遵循:@Autowired

爪哇岛
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
    HibernateTitleRepository titleRepository;

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

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

前面的代码清单使用批注引用的同一 XML 上下文文件(即 )。以下 显示以下配置:@ContextConfigurationrepository-config.xml

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

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

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

</beans>

如果您从 Spring 提供的测试基类扩展,而该基类恰好在其 setter 方法之一上使用,则可能有多个受影响的 bean 在应用程序上下文中定义的类型(例如,多个 Bean)。在 在这种情况下,您可以重写 setter 方法并使用注解 指示一个特定的目标 bean,如下所示(但请确保委托给被覆盖的 方法):@AutowiredDataSource@Qualifier

爪哇岛
Kotlin
// ...

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

// ...

指定的限定符值指示要注入的特定 bean, 将类型匹配集缩小到特定 Bean。其值与相应定义中的声明匹配。Bean 名称 用作回退限定符值,因此也可以有效地指向特定的 bean 按名称存在(如前所示,假设是 bean )。DataSource<qualifier><bean>myDataSourceid

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

Spring 支持请求范围和会话范围 Bean,您可以测试请求范围和会话范围 bean,请按照以下步骤操作:

  • 通过注释测试,确保为测试加载了 a 类。WebApplicationContext@WebAppConfiguration

  • 将模拟请求或会话注入到测试实例中并准备测试 根据需要固定。

  • 调用从配置的 Web 组件(使用依赖项注入)中检索到的 Web 组件。WebApplicationContext

  • 对模拟执行断言。

下一个代码片段显示了登录用例的 XML 配置。请注意,Bean 依赖于请求范围的 Bean。此外,通过使用 SpEL 表达式实例化 从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们希望 通过 TestContext 框架管理的 mock 配置这些请求参数。 以下列表显示了此用例的配置:userServiceloginActionLoginAction

请求范围的 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>

在 中,我们同时注入 (即主语 test) 并放入我们的测试实例中。在我们的测试方法中,我们通过在 提供的 .当在我们的 上调用该方法时,我们确信用户服务可以访问当前(即我们刚刚 设置参数)。然后,我们可以根据已知的结果执行断言 用户名和密码的输入。下面的清单显示了如何执行此操作:RequestScopedBeanTestsUserServiceMockHttpServletRequestrequestScope()MockHttpServletRequestloginUser()userServiceloginActionMockHttpServletRequest

爪哇岛
Kotlin
@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
    }
}

以下代码片段类似于我们之前看到的请求范围的代码片段 豆。但是,这一次,Bean 依赖于会话范围的 Bean。请注意,Bean 是使用 从当前 HTTP 会话中检索主题的 SpEL 表达式。在我们的测试中,我们 需要在 TestContext 框架管理的模拟会话中配置一个主题。这 以下示例演示如何执行此操作:userServiceuserPreferencesUserPreferences

会话范围的 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>

在 中,我们将 和 注入 我们的测试实例。在我们的测试方法中,我们通过以下方式设置测试夹具 在提供的 .当在我们的 上调用该方法时,我们可以确信 用户服务可以访问当前 的会话范围,我们可以根据 配置的主题。以下示例演示如何执行此操作:SessionScopedBeanTestsUserServiceMockHttpSessionsessionScope()themeMockHttpSessionprocessUserPreferences()userServiceuserPreferencesMockHttpSession

爪哇岛
Kotlin
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

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

        Results results = userService.processUserPreferences();
        // assert results
    }
}

3.5.9. 事务管理

在 TestContext 框架中,事务由 ,默认情况下会配置,即使您不这样做也是如此 在测试类上显式声明。启用对 但是,您必须在加载了语义的 bean 中配置一个 bean(进一步 详情见下文)。此外,您必须在测试的类或方法级别声明 Spring 的注解。TransactionalTestExecutionListener@TestExecutionListenersPlatformTransactionManagerApplicationContext@ContextConfiguration@Transactional

测试管理的事务

测试管理的事务是使用 以声明方式管理的事务,或者是以编程方式使用 (稍后介绍) 的事务。您不应将此类事务与 Spring 管理的事务混淆 事务(由 Spring 在 加载的 tests)或应用程序管理的事务(以编程方式管理的事务 由测试调用的应用程序代码)。Spring 托管和应用程序托管 事务通常参与测试管理的事务。但是,您应该使用 如果 Spring 管理的事务或应用程序管理的事务配置了任何 除 OR 以外的传播类型(有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListenerTestTransactionApplicationContextREQUIREDSUPPORTS

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

在测试框架中使用任何形式的抢占式超时,必须小心 与 Spring 的测试管理事务结合使用。

具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过 变量),然后调用当前测试方法。如果 测试框架在新线程中调用当前测试方法,以支持 抢占式超时,在当前测试方法中执行的任何操作都不会 在测试管理的事务中调用。因此,任何此类行动的结果 不会随测试管理的事务一起回滚。相反,这样的行为 将提交到持久性存储(例如,关系数据库)中,甚至 尽管测试管理的事务被 Spring 正确回滚。java.lang.ThreadLocal

发生这种情况的情况包括但不限于以下情况。

  • JUnit 4 的支持和规则@Test(timeout = …​)TimeOut

  • JUnit Jupiter 在类中的方法assertTimeoutPreemptively(…​)org.junit.jupiter.api.Assertions

  • TestNG的支持@Test(timeOut = …​)

启用和禁用事务

对测试方法进行注释会导致测试在 默认情况下,事务在测试完成后自动回滚。 如果测试类使用 ,则该类中的每个测试方法都带有注释 层次结构在事务中运行。未注释的测试方法(在类或方法级别)不会在事务中运行。注意 测试生命周期方法(例如,方法)不支持 用 JUnit Jupiter 的 、 等注释。此外,测试 已批注,但该属性已设置为或未在事务中运行。@Transactional@Transactional@Transactional@Transactional@BeforeAll@BeforeEach@TransactionalpropagationNOT_SUPPORTEDNEVER

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

valuetransactionManager

是的

propagation

仅且受支持Propagation.NOT_SUPPORTEDPropagation.NEVER

isolation

timeout

readOnly

rollbackForrollbackForClassName

否:改用TestTransaction.flagForRollback()

noRollbackFornoRollbackForClassName

否:改用TestTransaction.flagForCommit()

方法级生命周期方法(例如,用 JUnit Jupiter 的 or 注释的方法)在测试管理的事务中运行。另一方面 手、套件级和类级生命周期方法,例如,用 JUnit Jupiter 的 or 和用 TestNG 的 、 、 或 — 注释的方法不在 测试管理的事务。@BeforeEach@AfterEach@BeforeAll@AfterAll@BeforeSuite@AfterSuite@BeforeClass@AfterClass

如果需要在套件级别或类级别的生命周期方法中运行代码 事务中,您可能希望注入相应的 你的测试类,然后将其与 for programmatic 一起使用 事务管理。PlatformTransactionManagerTransactionTemplate

请注意,AbstractTransactionalJUnit4SpringContextTests AbstractTransactionalTestNGSpringContextTests 已针对类级别的事务支持进行了预配置。

以下示例演示了为 a 基于休眠:UserRepository

爪哇岛
Kotlin
@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"));
    }
}

事务回滚和提交行为中所述,没有必要 在方法运行后清理数据库,因为对 数据库由 自动回滚。createUser()TransactionalTestExecutionListener

事务回滚和提交行为

默认情况下,测试事务将在完成 测试;但是,事务提交和回滚行为可以通过声明方式进行配置 通过 和 注释。有关详细信息,请参阅注释支持部分中的相应条目。@Commit@Rollback

程序化事务管理

您可以使用静态 中的方法。例如,您可以在 test 中使用 方法、方法之前和方法之后开始或结束当前测试托管的方法 事务或配置当前测试管理的事务以进行回滚或提交。 每当启用时,都会自动提供对 的支持。TestTransactionTestTransactionTestTransactionTransactionalTestExecutionListener

下面的示例演示了 的一些功能。请参阅 javadoc for TestTransaction 了解更多详情。TestTransaction

爪哇岛
Kotlin
@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"));
    }
}
在事务外部运行代码

有时,您可能需要在事务测试之前或之后运行某些代码 方法,但在事务上下文之外 — 例如,验证初始 运行测试或验证预期事务提交之前的数据库状态 测试运行后的行为(如果测试配置为提交事务)。 支持此类场景的 AND 注解。您可以使用以下方法之一对测试类中的任何方法或测试接口中的任何默认方法进行注释 注解,并确保之前 事务方法或事务方法在适当的时间运行之后。TransactionalTestExecutionListener@BeforeTransaction@AfterTransactionvoidvoidTransactionalTestExecutionListener

任何 before 方法(例如用 JUnit Jupiter 注释的方法) 任何 after 方法(例如用 JUnit Jupiter 注释的方法)都是 在事务中运行。此外,对于未配置为在 交易。@BeforeEach@AfterEach@BeforeTransaction@AfterTransaction
配置事务管理器

TransactionalTestExecutionListener期望 Bean 是 在 Spring 中为测试定义。如果有多个实例 在测试中,你可以声明一个 通过使用 或 限定符,或者可以由类实现。查阅 javadoc for TestContextTransactionUtils.retrieveTransactionManager() 以获取有关 用于在测试的 .PlatformTransactionManagerApplicationContextPlatformTransactionManagerApplicationContext@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer@ConfigurationApplicationContext

演示所有与交易相关的注解

以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试 突出显示所有与事务相关的注释的场景。该示例不是故意的 演示最佳实践,而是演示这些注释如何 使用。有关进一步的信息,请参阅注释支持部分 信息和配置示例。@Sql的事务管理包含一个附加示例,该示例用于 具有默认事务回滚语义的声明性 SQL 脚本执行。这 以下示例显示了相关的注释:@Sql

爪哇岛
Kotlin
@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
    }

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

当您测试操作 Hibernate 会话或 JPA 状态的应用程序代码时 持久性上下文,请确保刷新测试方法中的基础工作单元 运行该代码。未能刷新基础工作单元可能会产生错误 positives:您的测试已通过,但相同的代码在实时生产中引发异常 环境。请注意,这适用于任何维护内存单元的 ORM 框架 的工作。在以下基于 Hibernate 的示例测试用例中,有一个方法演示了 误报,另一种方法会正确暴露刷新 会期:

爪哇岛
Kotlin
// ...

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

// ...

以下示例显示了 JPA 的匹配方法:

爪哇岛
Kotlin
// ...

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

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

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

例如,在使用 JPA、、 和回调时 除非在实体 已保存或更新。同样,如果实体已附加到当前工作单元 (与当前持久性上下文相关联),尝试重新加载实体将 不会导致回调,除非在 尝试重新加载实体。@PostPersist@PreUpdate@PostUpdateentityManager.flush()@PostLoadentityManager.clear()

以下示例演示如何刷新 以确保在持久化实体时调用回调。具有 已为 例。EntityManager@PostPersist@PostPersistPerson

爪哇岛
Kotlin
// ...

@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...
}

// ...

请参阅 Spring Framework 测试套件中的 JpaEntityListenerTests,了解使用所有 JPA 生命周期回调的工作示例。

3.5.10. 执行 SQL 脚本

在针对关系数据库编写集成测试时,通常对 运行 SQL 脚本以修改数据库结构或将测试数据插入到表中。该模块支持初始化嵌入式或现有数据库 通过在加载 Spring 时执行 SQL 脚本。请参阅嵌入式数据库支持使用 嵌入式数据库了解详细信息。spring-jdbcApplicationContext

尽管在加载 时初始化一次数据库进行测试非常有用,但有时必须能够修改 集成测试期间的数据库。以下各节说明如何运行 SQL 在集成测试期间以编程方式和声明方式编写脚本。ApplicationContext

以编程方式执行 SQL 脚本

Spring 提供了以下选项,用于在 集成测试方法。

  • org.springframework.jdbc.datasource.init.ScriptUtils

  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

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

ResourceDatabasePopulator提供基于对象的 API,用于以编程方式填充, 使用外部中定义的 SQL 脚本初始化或清理数据库 资源。 提供用于配置字符的选项 编码、语句分隔符、注释分隔符和错误处理标志在以下情况下使用 解析和运行脚本。每个配置选项都有一个合理的 默认值。请参阅 javadoc 有关默认值的详细信息。要运行中配置的脚本,可以调用以下任一方法 针对 或 方法运行 populator 以针对 .以下示例 指定测试架构和测试数据的 SQL 脚本,将语句分隔符设置为 ,然后针对 :ResourceDatabasePopulatorResourceDatabasePopulatorpopulate(Connection)java.sql.Connectionexecute(DataSource)javax.sql.DataSource@@DataSource

爪哇岛
Kotlin
@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
}

请注意,内部委托给 for 解析 并运行 SQL 脚本。同样,AbstractTransactionalJUnit4SpringContextTests AbstractTransactionalTestNGSpringContextTests 中的方法在内部使用 a 来运行 SQL 脚本。请参阅 Javadoc 中 各种方法以获取更多详细信息。ResourceDatabasePopulatorScriptUtilsexecuteSqlScript(..)ResourceDatabasePopulatorexecuteSqlScript(..)

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

除了前面提到的以编程方式运行 SQL 脚本的机制之外, 您可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。 具体来说,可以将测试类或测试方法的注释声明为 配置单个 SQL 语句或应为 SQL 脚本的资源路径 在集成测试方法之前或之后针对给定数据库运行。对 的支持由 提供,默认情况下处于启用状态。@Sql@SqlSqlScriptsTestExecutionListener

默认情况下,方法级声明会重写类级声明。如 但是,此行为可以按测试类或每个 测试方法通过 .有关详细信息,请参阅使用 @SqlMergeMode 合并和覆盖配置@Sql@SqlMergeMode
路径资源语义

每条路径都被解释为 Spring 。普通路径(例如,)被视为相对于 定义测试类。以斜杠开头的路径被视为绝对路径 类路径资源(例如,)。引用 URL(例如,以 , , 为前缀的路径)是使用 指定的资源协议。Resource"schema.sql""/org/example/schema.sql"classpath:file:http:

下面的示例演示如何在类级别和方法级别使用 在基于 JUnit Jupiter 的集成测试类中:@Sql

爪哇岛
Kotlin
@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
    }
}
默认脚本检测

如果未指定 SQL 脚本或语句,则会尝试检测脚本,具体取决于声明的位置。如果无法检测到默认值,则抛出 。default@SqlIllegalStateException

  • 类级声明:如果带注释的测试类是 , 对应的默认脚本是 。com.example.MyTestclasspath:com/example/MyTest.sql

  • 方法级声明:如果带注释的测试方法已命名并且是 在类中定义,对应的默认脚本是 。testMethod()com.example.MyTestclasspath:com/example/MyTest.testMethod.sql

声明多个集合@Sql

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

以下示例显示了如何在 Java 8 中用作可重复的注解:@Sql

爪哇岛
Kotlin
@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
}

在前面示例中介绍的方案中,该脚本使用 单行注释的不同语法。test-schema.sql

以下示例与前面的示例相同,只是声明在 中组合在一起。在 Java 8 及更高版本中,使用 是可选的,但您可能需要使用 以兼容 其他 JVM 语言,例如 Kotlin。@Sql@SqlGroup@SqlGroup@SqlGroup

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

默认情况下,SQL 脚本在相应的测试方法之前运行。但是,如果 您需要在测试方法之后运行一组特定的脚本(例如,要清理 up database state),您可以使用 中的属性作为 以下示例显示:executionPhase@Sql

爪哇岛
Kotlin
@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
}

请注意,和分别是从 和 静态导入的。ISOLATEDAFTER_TEST_METHODSql.TransactionModeSql.ExecutionPhase

脚本配置@SqlConfig

您可以使用注释来配置脚本解析和错误处理。 当声明为集成测试类的类级注释时,用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候 使用注释的属性直接声明,用作在封闭注释中声明的 SQL 脚本的本地配置。中的每个属性都有一个隐式默认值,即 记录在相应属性的 javadoc 中。由于定义的规则 Java 语言规范中的注解属性,不幸的是,它不是 可以为注释属性赋值。因此,为了 支持覆盖继承的全局配置,属性具有 显式默认值 (for Strings)、(for arrays) 或 (for 枚举)。此方法允许有选择地重写本地声明 来自全局声明的单个属性,通过提供其他值 比 、 或 。全局属性在以下情况下被继承 局部属性不提供除 、 或 以外的显式值。因此,显式本地配置将覆盖全局配置。@SqlConfig@SqlConfigconfig@Sql@SqlConfig@Sql@SqlConfignull@SqlConfig""{}DEFAULT@SqlConfig@SqlConfig""{}DEFAULT@SqlConfig@SqlConfig""{}DEFAULT

提供的配置选项 和 等效于 由 和 支持,但 是它们的超集 由 XML 命名空间元素提供。请参阅 javadoc 的 @Sql中的各个属性,有关详细信息,@SqlConfig@Sql@SqlConfigScriptUtilsResourceDatabasePopulator<jdbc:initialize-database/>

@Sql事务管理

默认情况下,推断所需的事务 使用 配置的脚本的语义。具体而言,运行 SQL 脚本 没有事务,在现有的 Spring 管理事务中(例如,一个 事务由 for 测试管理,并用 ) 注释,或在独立事务中管理,具体取决于配置的值 的属性和测试中 a 的存在。最低限度, 但是,测试的 .SqlScriptsTestExecutionListener@SqlTransactionalTestExecutionListener@TransactionaltransactionMode@SqlConfigPlatformTransactionManagerApplicationContextjavax.sql.DataSourceApplicationContext

如果用于检测 和 并推断事务语义的算法不符合您的需求, 您可以通过设置 的 和 属性来指定显式名称。此外,您可以控制事务传播 行为,通过设置 的属性(例如,是否 脚本应在独立事务中运行)。虽然对所有 事务管理支持的选项超出了此范围 参考手册、javadoc for @SqlConfigSqlScriptsTestExecutionListener 提供了详细信息,以下示例显示了一个典型的测试场景 使用 JUnit Jupiter 和事务测试:SqlScriptsTestExecutionListenerDataSourcePlatformTransactionManagerdataSourcetransactionManager@SqlConfigtransactionMode@SqlConfig@Sql@Sql

爪哇岛
Kotlin
@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.");
    }
}

请注意,在方法 运行,因为对数据库所做的任何更改(无论是在测试方法中还是在脚本中)都会由 (请参阅事务管理 详细信息)。usersTest()/test-data.sqlTransactionalTestExecutionListener

合并和覆盖配置@SqlMergeMode

从 Spring Framework 5.2 开始,可以将方法级声明与 类级声明。例如,这允许您提供 数据库模式或一些常见的测试数据,每个测试类一次,然后提供额外的, 每个测试方法的用例特定测试数据。要启用合并,请对任一内容进行注释 带有 的测试类或测试方法。禁用合并 特定的测试方法(或特定的测试子类),可以切换回默认模式 通过。有关示例和更多详细信息,请参阅@SqlMergeMode注释文档部分@Sql@Sql@SqlMergeMode(MERGE)@SqlMergeMode(OVERRIDE)

3.5.11. 并行测试执行

Spring Framework 5.0 引入了对在 使用 Spring TestContext Framework 时的单个 JVM。一般来说,这意味着大多数 测试类或测试方法可以并行运行,而无需对测试代码进行任何更改 或配置。

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

请记住,在测试套件中引入并发可能会导致 意外的副作用、奇怪的运行时行为以及间歇性失败的测试,或者 看似随机。因此,Spring 团队提供了以下一般准则 何时不并行运行测试。

如果测试出现以下情况,则不要并行运行测试:

  • 使用 Spring Framework 的支持。@DirtiesContext

  • 使用 Spring Boot 或支持。@MockBean@SpyBean

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

  • 更改共享服务或系统(如数据库、消息代理、 文件系统等。这适用于嵌入式和外部系统。

如果并行测试执行失败并出现异常,指出当前测试不再处于活动状态,这通常意味着已从其他线程中删除。ApplicationContextApplicationContextContextCache

这可能是由于使用了 或由于自动逐出 .如果是罪魁祸首,你要么需要想办法 避免使用此类测试或从并行执行中排除此类测试。如果 已超过最大大小,可以增加最大大小 的缓存。有关详细信息,请参阅有关上下文缓存的讨论。@DirtiesContextContextCache@DirtiesContext@DirtiesContextContextCache

只有在以下情况下,才能在 Spring TestContext Framework 中并行执行测试 基础实现提供了一个复制构造函数,如 TestContext 的 javadoc。Spring 中使用的构造函数提供了这样一个构造函数。但是,如果您使用 提供自定义实现的第三方库,您需要 验证它是否适合并行测试执行。TestContextDefaultTestContextTestContext

3.5.12. TestContext框架支持类

本节介绍支持 Spring TestContext Framework 的各种类。

Spring JUnit 4 运行程序

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

下面的代码清单显示了将测试类配置为 使用自定义 Spring 运行:Runner

爪哇岛
Kotlin
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // test logic...
    }
}

在前面的示例中,配置了一个空列表,以 禁用默认侦听器,否则需要 通过 进行配置。@TestExecutionListenersApplicationContext@ContextConfiguration

Spring JUnit 4 规则

该软件包提供以下 JUnit 4 条规则(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4.rules

  • SpringClassRule

  • SpringMethodRule

SpringClassRule是一个 JUnit,它支持 Spring 的类级特性 TestContext 框架,而是一个支持 Spring TestContext Framework 的实例级和方法级功能。TestRuleSpringMethodRuleMethodRule

与 相比,Spring 基于规则的 JUnit 支持具有以下优势 独立于任何实现,因此可以 与现有的替代运行器(例如 JUnit 4 )结合使用,或 第三方运行器(例如 )。SpringRunnerorg.junit.runner.RunnerParameterizedMockitoJUnitRunner

要支持 TestContext 框架的全部功能,必须将 与 .以下示例显示了正确的方法 要在集成测试中声明这些规则,请执行以下操作:SpringClassRuleSpringMethodRule

爪哇岛
Kotlin
// 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...
    }
}
JUnit 4 支持类

该软件包提供以下支持 基于 JUnit 4 的测试用例的类(在 JUnit 4.12 或更高版本上受支持):org.springframework.test.context.junit4

  • AbstractJUnit4SpringContextTests

  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests是一个抽象基测试类,它集成了 Spring TestContext 框架,在 JUnit 4 环境。扩展时,可以访问可用于执行显式执行的实例变量 Bean 查找或测试整个上下文的状态。ApplicationContextAbstractJUnit4SpringContextTestsprotectedapplicationContext

AbstractTransactionalJUnit4SpringContextTests是一个抽象的事务扩展,它为 JDBC 增加了一些便利功能 访问。此类需要在 中定义一个 Bean 和一个 Bean。当你 extend ,您可以访问一个实例变量,该变量可用于运行 SQL 语句以查询 数据库。您可以使用此类查询来确认之前和之后的数据库状态 运行与数据库相关的应用程序代码,Spring 确保此类查询在 与应用程序代码相同的事务的范围。当与 一个 ORM 工具,一定要避免误报。 如JDBC测试支持中所述,还提供了方便的方法 通过使用上述 . 此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractJUnit4SpringContextTestsjavax.sql.DataSourcePlatformTransactionManagerApplicationContextAbstractTransactionalJUnit4SpringContextTestsprotectedjdbcTemplateAbstractTransactionalJUnit4SpringContextTestsJdbcTestUtilsjdbcTemplateAbstractTransactionalJUnit4SpringContextTestsexecuteSqlScript(..)DataSource

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

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

此外,由于 JUnit Jupiter 中丰富的扩展 API,Spring 提供了 除了 Spring 对 JUnit 4 支持的功能集之外,还具有以下功能。 测试NG:

  • 测试构造函数、测试方法和测试生命周期回调的依赖关系注入 方法。有关更多详细信息,请参阅使用 SpringExtension 进行依赖注入

  • 对条件的强大支持 基于 SpEL 表达式、环境变量、系统属性、 等等。有关更多详细信息和示例,请参阅 Spring JUnit Jupiter 测试注解的文档和 Spring JUnit Jupiter 测试注解中的文档。@EnabledIf@DisabledIf

  • 自定义组合的注解,结合了来自 Spring 和 JUnit Jupiter 的注解。看 和 测试的元注释支持中的示例,以获取更多详细信息。@TransactionalDevTestConfig@TransactionalIntegrationTest

下面的代码清单显示了如何配置测试类以将 与 结合使用 :SpringExtension@ContextConfiguration

爪哇岛
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
    void testMethod() {
        // test logic...
    }
}

由于您还可以在 JUnit 5 中使用注解作为元注解,因此 Spring 提供了 和 组合注解来简化 测试和 JUnit Jupiter 的配置。@SpringJUnitConfig@SpringJUnitWebConfigApplicationContext

以下示例用于减少配置量 在前面的示例中使用:@SpringJUnitConfig

爪哇岛
Kotlin
// 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...
    }
}

同样,以下示例用于创建用于 JUnit Jupiter 的示例:@SpringJUnitWebConfigWebApplicationContext

爪哇岛
Kotlin
// 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...
    }
}

有关更多详细信息,请参阅 Spring JUnit Jupiter 测试注解的文档和 Spring JUnit Jupiter 测试注解中的文档。@SpringJUnitConfig@SpringJUnitWebConfig

依赖注入SpringExtension

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

具体来说,可以将测试中的依赖项注入到测试构造函数和方法中,这些构造函数和方法用 、 、 、 、 等进行注释。SpringExtensionApplicationContext@BeforeAll@AfterAll@BeforeEach@AfterEach@Test@RepeatedTest@ParameterizedTest

构造函数注入

如果 JUnit Jupiter 测试类的构造函数中的特定参数属于类型(或其子类型),或者用 、 或 进行注释或元注释,则 Spring 会注入该特定参数的值 参数替换为测试的相应 bean 或值。ApplicationContext@Autowired@Qualifier@ValueApplicationContext

如果出现以下情况,Spring 也可以配置为自动连接测试类构造函数的所有参数 构造函数被认为是可自动连接的。构造函数被认为是 如果满足以下条件之一(按优先顺序),则可自动接线。

  • 构造函数用 批注。@Autowired

  • @TestConstructor在属性设置为 的测试类上存在或元存在。autowireModeALL

  • 默认测试构造函数 autowire 模式已更改为 。ALL

有关使用以及如何更改全局测试构造函数自动连线模式的详细信息,请参阅@TestConstructor@TestConstructor

如果测试类的构造函数被认为是可自动连接的,则 Spring 负责解析构造函数中所有参数的参数。 因此,没有其他在 JUnit Jupiter 注册的人可以解析 此类构造函数的参数。ParameterResolver

测试类的构造函数注入不得与 JUnit 结合使用 Jupiter 的支持 if 用于关闭 测试之前或之后的测试方法。@TestInstance(PER_CLASS)@DirtiesContextApplicationContext

原因是指示 JUnit Jupiter 缓存测试 测试方法调用之间的实例。因此,测试实例将保留 对最初从具有 随后关闭。由于测试类的构造函数只会被调用 一旦出现这种情况,依赖注入将不会再次发生,后续测试 将与关闭的 bean 交互,这可能会导致错误。@TestInstance(PER_CLASS)ApplicationContextApplicationContext

与“测试前”或“测试后”模式一起使用 结合使用,必须从 Spring 配置依赖关系 通过现场或入孵机注入提供,以便它们可以在测试之间重新注入 方法调用。@DirtiesContext@TestInstance(PER_CLASS)

在下面的示例中,Spring 将 bean 从 loaded from 注入到构造函数中。OrderServiceApplicationContextTestConfig.classOrderServiceIntegrationTests

爪哇岛
Kotlin
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

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

    // tests that use the injected OrderService
}

请注意,此功能允许测试依赖项是不可变的,因此是不可变的。final

如果属性是 to(参见 @TestConstructor),我们可以省略上一个示例中构造函数的声明,从而产生以下结果。spring.test.constructor.autowire.modeall@Autowired

爪哇岛
Kotlin
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

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

    // tests that use the injected OrderService
}
方法注入

如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数为 type(或其子类型)或用 、 或 、 或 进行注释或元注释 Spring 注入该特定 参数替换为测试的 .ApplicationContext@Autowired@Qualifier@ValueApplicationContext

在以下示例中,Spring 将 from 加载的 from 注入到 test 方法中:OrderServiceApplicationContextTestConfig.classdeleteOrder()

爪哇岛
Kotlin
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

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

由于 JUnit Jupiter 中支持的健壮性,您还可以 将多个依赖项注入到单个方法中,不仅来自 Spring,还来自 来自 JUnit Jupiter 本身或其他第三方扩展。ParameterResolver

以下示例显示了如何让 Spring 和 JUnit Jupiter 注入依赖项 同时进入测试方法。placeOrderRepeatedly()

爪哇岛
Kotlin
@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
    }
}

请注意,使用 from JUnit Jupiter 可以让测试方法获得访问权限 到 .@RepeatedTestRepetitionInfo

@Nested测试类配置

自 Spring Framework 5.0 以来,Spring TestContext Framework 支持在 JUnit Jupiter 中的测试类上使用与测试相关的注释;然而,直到春天 Framework 5.3 类级测试配置注释不是 像来自超类一样封闭类。@Nested

Spring Framework 5.3 引入了对继承测试类的一流支持 配置,并且此类配置将由 违约。要从默认模式更改为模式,您可以注释 带有 的单个测试类。显式声明将应用于带注释的测试类以及 它的任何子类和嵌套类。因此,您可以对顶级测试类进行注释 替换为 ,这将适用于其所有嵌套测试类 递 归。INHERITOVERRIDE@Nested@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)@NestedTestConfiguration@NestedTestConfiguration

为了允许开发团队将默认值更改为 - 例如, 为了与 Spring Framework 5.0 到 5.2 兼容 – 可以更改默认模式 全局通过 JVM 系统属性或根目录中的文件 类路径。请参阅“更改 默认封闭配置继承模式“的注释。OVERRIDEspring.properties

尽管下面的“Hello World”示例非常简单,但它显示了如何声明 由其测试继承的顶级类的通用配置 类。在此特定示例中,只有配置类是 继承。每个嵌套测试类都提供自己的一组活动配置文件,从而产生 每个嵌套测试类都是不同的(有关详细信息,请参阅上下文缓存)。请参阅支持的注释列表以查看 哪些注解可以在测试类中继承。@NestedTestConfigApplicationContext@Nested

爪哇岛
Kotlin
@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");
        }
    }
}
TestNG 支持类

该软件包提供以下支持 基于 TestNG 的测试用例的类:org.springframework.test.context.testng

  • AbstractTestNGSpringContextTests

  • AbstractTransactionalTestNGSpringContextTests

AbstractTestNGSpringContextTests是一个抽象基测试类,它集成了 Spring TestContext 框架,在 TestNG 环境。扩展时,可以访问可用于执行显式执行的实例变量 Bean 查找或测试整个上下文的状态。ApplicationContextAbstractTestNGSpringContextTestsprotectedapplicationContext

AbstractTransactionalTestNGSpringContextTests是一个抽象的事务扩展,它为 JDBC 增加了一些便利功能 访问。此类需要在 中定义一个 Bean 和一个 Bean。当你 extend ,您可以访问一个实例变量,该变量可用于运行 SQL 语句以查询 数据库。您可以使用此类查询来确认之前和之后的数据库状态 运行与数据库相关的应用程序代码,Spring 确保此类查询在 与应用程序代码相同的事务的范围。当与 一个 ORM 工具,一定要避免误报。 如JDBC测试支持中所述,还提供了方便的方法 通过使用上述 . 此外,还提供了一种针对配置的 SQL 脚本运行 SQL 脚本的方法。AbstractTestNGSpringContextTestsjavax.sql.DataSourcePlatformTransactionManagerApplicationContextAbstractTransactionalTestNGSpringContextTestsprotectedjdbcTemplateAbstractTransactionalTestNGSpringContextTestsJdbcTestUtilsjdbcTemplateAbstractTransactionalTestNGSpringContextTestsexecuteSqlScript(..)DataSource

这些类是扩展的便利。如果你不想要你的测试类 要绑定到特定于 Spring 的类层次结构,您可以配置自己的自定义测试 类通过使用 、 等 和 使用 .查看源代码 的示例,说明如何检测测试类。@ContextConfiguration@TestExecutionListenersTestContextManagerAbstractTestNGSpringContextTests

3.6. WebTestClient

WebTestClient是专为测试服务器应用程序而设计的 HTTP 客户端。它包裹 Spring 的 WebClient 并使用它来执行请求 但公开了一个用于验证响应的测试外观。 可用于 执行端到端 HTTP 测试。它还可用于测试 Spring MVC 和 Spring WebFlux 没有正在运行的服务器的应用程序,通过模拟服务器请求和响应对象。WebTestClient

Kotlin 用户:请参阅 .WebTestClient

3.6.1. 设置

要设置一个,您需要选择要绑定到的服务器设置。这可以是一个 的几个模拟服务器设置选项或与实时服务器的连接。WebTestClient

绑定到控制器

此设置允许您通过模拟请求和响应对象测试特定控制器, 没有正在运行的服务器。

对于 WebFlux 应用程序,使用以下命令加载与 WebFlux Java 配置等效的基础架构,注册给定的 控制器,并创建一个 WebHandler 链来处理请求:

爪哇岛
Kotlin
WebTestClient client =
        WebTestClient.bindToController(new TestController()).build();

对于 Spring MVC,使用以下委托给 StandaloneMockMvcBuilder 来加载与 WebMvc Java 配置等效的基础结构, 注册给定的控制器,并创建一个 MockMvc 实例来处理请求:

爪哇岛
Kotlin
WebTestClient client =
        MockMvcWebTestClient.bindToController(new TestController()).build();
绑定到ApplicationContext

此设置允许您使用 Spring MVC 或 Spring WebFlux 加载 Spring 配置 基础架构和控制器声明,并使用它来通过模拟请求处理请求 和响应对象,没有正在运行的服务器。

对于 WebFlux,使用以下命令,其中 Spring 被传递给 WebHttpHandlerBuilder 以创建要处理的 WebHandler 链 请求:ApplicationContext

爪哇岛
Kotlin
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {

    WebTestClient client;

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

对于 Spring MVC,使用以下命令将 Spring 传递给 MockMvcBuilders.webAppContextSetup 以创建要处理的 MockMvc 实例 请求:ApplicationContext

爪哇岛
Kotlin
@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
绑定到路由器功能

此设置允许你通过以下方式测试功能终结点 模拟请求和响应对象,没有正在运行的服务器。

对于 WebFlux,使用以下命令将委托给 创建服务器设置以处理请求:RouterFunctions.toWebHandler

爪哇岛
Kotlin
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();

对于 Spring MVC,目前没有测试 WebMvc 功能端点的选项。

绑定到服务器

此设置连接到正在运行的服务器以执行完整的端到端 HTTP 测试:

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

除了前面描述的服务器设置选项外,还可以配置客户端 选项,包括基本 URL、默认标头、客户端过滤器等。这些选项 在以下情况下很容易获得。对于所有其他配置选项, 您需要使用从服务器配置过渡到客户端配置,因为 遵循:bindToServer()configureClient()

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

3.6.2. 编写测试

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

在调用 之后,与 和 发散 而是继续执行工作流来验证响应。exchange()WebTestClientWebClient

要断言响应状态和标头,请使用以下命令:

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

如果您希望所有期望都能得到实现,即使其中一个失败了,您可以 使用而不是多个链接调用。此功能是 类似于 AssertJ 中的软断言支持和 JUnit 木星。expectAll(..)expect*(..)assertAll()

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

然后,您可以选择通过以下方法之一对响应正文进行解码:

  • expectBody(Class<T>):解码为单个对象。

  • expectBodyList(Class<T>):解码对象并将其收集到 。List<T>

  • expectBody():解码为 JSON 内容或空正文。byte[]

并对生成的更高级别的对象执行断言:

爪哇岛
Kotlin
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList(Person.class).hasSize(3).contains(person);

如果内置断言不足,则可以改为使用对象,然后 执行任何其他断言:

爪哇岛
Kotlin
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)...
        });

或者,您可以退出工作流并获取:EntityExchangeResult

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

如果响应不应包含内容,则可以按如下方式断言:

爪哇岛
Kotlin
client.post().uri("/persons")
        .body(personMono, Person.class)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty();

如果要忽略响应内容,则释放不带响应内容的内容 任何断言:

爪哇岛
Kotlin
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound()
        .expectBody(Void.class);
JSON 内容

您可以在没有目标类型的情况下对原始数据库执行断言 内容,而不是通过更高级别的对象。expectBody()

要使用 JSONAssert 验证完整的 JSON 内容,请执行以下操作:

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

要使用 JSONPath 验证 JSON 内容,请执行以下操作:

爪哇岛
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

爪哇岛
Kotlin
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult(MyEvent.class);

现在,你已准备好使用来自以下位置的响应流:StepVerifierreactor-test

爪哇岛
Kotlin
Flux<Event> eventFlux = result.getResponseBody();

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

WebTestClient是一个 HTTP 客户端,因此它只能验证客户端中的内容 响应,包括状态、标头和正文。

使用 MockMvc 服务器设置测试 Spring MVC 应用程序时,您有额外的 选择对服务器响应执行进一步的断言。要做到这一点,首先 在断言正文后获取:ExchangeResult

爪哇岛
Kotlin
// 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();

然后切换到 MockMvc 服务器响应断言:

爪哇岛
Kotlin
MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));

3.7. 模拟Mvc

Spring MVC 测试框架(也称为 MockMvc)为测试 Spring 提供支持 MVC 应用程序。它执行完整的 Spring MVC 请求处理,但通过模拟请求和 响应对象,而不是正在运行的服务器。

MockMvc 可以单独用于执行请求和验证响应。它也可以是 通过 WebTestClient 使用,其中 MockMvc 作为服务器插入以处理 请求。优点是可以选择与更高级别的工作 对象而不是原始数据,以及切换到完整的端到端 HTTP 的能力 针对实时服务器进行测试,并使用相同的测试 API。WebTestClient

3.7.1. 概述

你可以通过实例化一个控制器,注入它来为Spring MVC编写普通的单元测试。 并调用其方法。但是,此类测试不会验证请求 映射、数据绑定、消息转换、类型转换、验证和 NOR 它们是否涉及任何支持、或方法。@InitBinder@ModelAttribute@ExceptionHandler

Spring MVC 测试框架,也称为 ,旨在提供更完整的 在没有正在运行的服务器的情况下测试 Spring MVC 控制器。它通过调用 并从模块中传递 Servlet API 的“模拟”实现,该模块复制了完整的 Spring MVC 请求处理,而无需 正在运行的服务器。MockMvcDispatcherServletspring-test

MockMvc 是一个服务器端测试框架,可让您验证大多数功能 使用轻量级和有针对性的测试的 Spring MVC 应用程序。您可以在以下位置使用它 它自己来执行请求和验证响应,或者您也可以通过 插入了 MockMvc 的 WebTestClient API 作为服务器来处理请求 跟。

静态导入

直接使用 MockMvc 执行请求时,需要静态导入:

  • MockMvcBuilders.*

  • MockMvcRequestBuilders.*

  • MockMvcResultMatchers.*

  • MockMvcResultHandlers.*

记住这一点的一种简单方法是搜索 .如果使用 Eclipse,请确保也 在 Eclipse 首选项中将上述内容添加为 “favorite static members”。MockMvc*

通过 WebTestClient 使用 MockMvc 时,不需要静态导入。 提供流畅的 API,无需静态导入。WebTestClient

设置选择

MockMvc 可以通过以下两种方式之一进行设置。一种是直接指向您的控制器 想要测试和编程配置 Spring MVC 基础结构。二是 指向包含 Spring MVC 和控制器基础结构的 Spring 配置。

要设置 MockMvc 以测试特定控制器,请使用以下命令:

爪哇岛
Kotlin
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

}

或者,您也可以在通过委托给同一构建器的 WebTestClient 进行测试时使用此设置 如上图所示。

要通过 Spring 配置设置 MockMvc,请使用以下命令:

爪哇岛
Kotlin
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}

或者,您也可以在通过委托给同一构建器的 WebTestClient 进行测试时使用此设置 如上图所示。

您应该使用哪个设置选项?

加载实际的 Spring MVC 配置,从而产生更多 完成集成测试。由于 TestContext 框架缓存了加载的 Spring 配置,它有助于保持测试快速运行,即使您在 测试套件。此外,您可以通过 Spring 将模拟服务注入到控制器中 配置,以继续专注于测试 Web 层。以下示例声明 使用 Mockito 的模拟服务:webAppContextSetup

<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

然后,您可以将模拟服务注入到测试中,以设置和验证 预期,如以下示例所示:

爪哇岛
Kotlin
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {

    @Autowired
    AccountService accountService;

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}

另一方面,它更接近于单元测试。它测试一个 一次控制器。您可以手动为控制器注入模拟依赖项,以及 它不涉及加载 Spring 配置。此类测试更侧重于风格 并更容易查看正在测试的控制器,是否有任何特定的 Spring 需要 MVC 配置才能工作,依此类推。这也是一个非常 编写临时测试以验证特定行为或调试问题的便捷方法。standaloneSetupstandaloneSetup

与大多数“集成与单元测试”的争论一样,没有对错之分 答。但是,使用 确实意味着需要进行额外的测试以验证您的 Spring MVC 配置。 或者,您可以使用 编写所有测试,以便始终 针对实际的 Spring MVC 配置进行测试。standaloneSetupwebAppContextSetupwebAppContextSetup

设置功能

无论您使用哪种 MockMvc 构建器,所有实现都提供 一些常见且非常有用的功能。例如,您可以声明一个标头 所有请求,并期望状态为 200 以及所有请求中的标头 响应,如下:MockMvcBuilderAcceptContent-Type

爪哇岛
Kotlin
// 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();

此外,第三方框架(和应用程序)可以预先打包设置 指令,例如 .Spring Framework 就有一个这样的 内置实现,有助于跨请求保存和重用 HTTP 会话。 您可以按如下方式使用它:MockMvcConfigurer

爪哇岛
Kotlin
// static import of SharedHttpSessionConfigurer.sharedHttpSession

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
        .apply(sharedHttpSession())
        .build();

// Use mockMvc to perform requests...

有关所有 MockMvc 构建器功能的列表,请参见 ConfigurableMockMvcBuilder 的 javadoc,或使用 IDE 浏览可用选项。

执行请求

本部分介绍如何单独使用 MockMvc 来执行请求和验证响应。 如果通过 MockMvc 使用 MockMvc,请参阅编写测试的相应部分。WebTestClient

执行使用任何 HTTP 方法的请求,如以下示例所示:

爪哇岛
Kotlin
// static import of MockMvcRequestBuilders.*

mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));

您还可以执行内部使用的文件上传请求,以便不实际解析多部分 请求。相反,您必须将其设置为类似于以下示例:MockMultipartHttpServletRequest

爪哇岛
Kotlin
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));

可以在 URI 模板样式中指定查询参数,如以下示例所示:

爪哇岛
Kotlin
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));

您还可以添加表示查询或表单的 Servlet 请求参数 参数,如以下示例所示:

爪哇岛
Kotlin
mockMvc.perform(get("/hotels").param("thing", "somewhere"));

如果应用程序代码依赖于 Servlet 请求参数,并且不检查查询 字符串显式(最常见的情况),使用哪个选项并不重要。 但请记住,随 URI 模板提供的查询参数是解码的 而通过该方法提供的请求参数预计已经 被解码。param(…​)

在大多数情况下,最好将上下文路径和 Servlet 路径排除在 请求 URI。如果必须使用完整的请求 URI 进行测试,请确保相应地设置 and,以便请求映射正常工作,如以下示例所示 显示:contextPathservletPath

爪哇岛
Kotlin
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))

在前面的示例中,为每个执行的请求设置 and 会很麻烦。相反,您可以设置默认请求 属性,如以下示例所示:contextPathservletPath

爪哇岛
Kotlin
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}

上述属性会影响通过实例执行的每个请求。 如果在给定请求中还指定了相同的属性,则它将覆盖默认值 价值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为 必须在每个请求中指定它们。MockMvc

定义期望

您可以通过在 执行请求,如以下示例所示。一旦一个期望落空, 不会断言其他期望。andExpect(..)

爪哇岛
Kotlin
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());

您可以通过在执行 请求,如以下示例所示。与 相反,保证所有提供的期望都将被断言,并且 所有故障都将被跟踪和报告。andExpectAll(..)andExpect(..)andExpectAll(..)

爪哇岛
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpectAll(
    status().isOk(),
    content().contentType("application/json;charset=UTF-8"));

MockMvcResultMatchers.*提供了许多期望,其中一些是更进一步的 嵌套了更详细的期望。

期望分为两大类。第一类断言验证 响应的属性(例如,响应状态、标头和内容)。 这些是最重要的结果。

第二类断言超出了响应的范围。这些断言可以让你 检查 Spring MVC 的具体方面,例如哪个控制器方法处理了 请求,是否引发和处理异常,模型的内容是什么, 选择了什么视图,添加了哪些闪存属性,等等。他们还让你 检查 Servlet 特定方面,例如请求和会话属性。

以下测试断言绑定或验证失败:

爪哇岛
Kotlin
mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));

很多时候,在编写测试时,转储执行的结果很有用 请求。您可以按如下方式执行此操作,其中是静态导入:print()MockMvcResultHandlers

爪哇岛
Kotlin
mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));

只要请求处理不会导致未处理的异常,该方法 将所有可用的结果数据打印到 。还有一种方法和 该方法的另外两个变体,一个接受 和 一个接受 .例如,调用打印结果 data 到 ,同时调用将结果数据打印到自定义 作家。如果要记录结果数据而不是打印结果数据,可以调用该方法,该方法将结果数据记录为日志记录类别下的单个消息。print()System.outlog()print()OutputStreamWriterprint(System.err)System.errprint(myWriter)log()DEBUGorg.springframework.test.web.servlet.result

在某些情况下,您可能希望直接访问结果并验证以下内容 无法以其他方式进行验证。毕竟,这可以通过附加来实现 其他期望,如以下示例所示:.andReturn()

爪哇岛
Kotlin
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...

如果所有测试都重复相同的期望,则可以在以下情况下设置一次共同期望 生成实例,如以下示例所示:MockMvc

爪哇岛
Kotlin
standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()

请注意,共同的期望总是被应用,如果没有,就不能被覆盖 创建单独的实例。MockMvc

当 JSON 响应内容包含使用 Spring HATEOAS 创建的超媒体链接时,您可以验证 使用 JsonPath 表达式生成的链接,如以下示例所示:

爪哇岛
Kotlin
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));

当XML响应内容包含使用Spring HATEOAS创建的超媒体链接时,可以验证 使用 XPath 表达式生成的链接:

爪哇岛
Kotlin
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"));
异步请求

本部分介绍如何单独使用 MockMvc 来测试异步请求处理。 如果通过 WebTestClient 使用 MockMvc,则无需执行任何特殊操作 异步请求的工作方式是自动执行所描述的操作 在本节中。WebTestClient

Spring MVC 中支持的 Servlet 3.0 异步请求通过退出 Servlet 容器来工作 线程,并允许应用程序异步计算响应,然后 进行异步分派以完成 Servlet 容器线程上的处理。

在 Spring MVC Test 中,可以通过断言生成的异步值来测试异步请求 首先,手动执行异步调度,最后验证响应。 下面是返回 , , 的控制器方法的示例测试 或反应型,如反应器:DeferredResultCallableMono

爪哇岛
Kotlin
// 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 验证最终响应
流式响应

测试流式响应(如服务器发送的事件)的最佳方式是通过 WebTestClient,它可以用作连接到实例的测试客户端 在没有正在运行的服务器的情况下在 Spring MVC 控制器上执行测试。例如:MockMvc

爪哇岛
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();

FluxExchangeResult<Person> exchangeResult = client.get()
        .uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType("text/event-stream")
        .returnResult(Person.class);

// Use StepVerifier from Project Reactor to test the streaming response

StepVerifier.create(exchangeResult.getResponseBody())
        .expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
        .expectNextCount(4)
        .consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
        .thenCancel()
        .verify();

WebTestClient还可以连接到实时服务器并执行完整的端到端集成 测试。Spring Boot 也支持此功能,您可以在其中测试正在运行的服务器

筛选注册

设置实例时,可以注册一个或多个 Servlet 实例,如以下示例所示:MockMvcFilter

爪哇岛
Kotlin
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();

已注册的过滤器通过 from 调用,并且 最后一个过滤器委托给 .MockFilterChainspring-testDispatcherServlet

MockMvc 与端到端测试

MockMVc 基于模块中的 Servlet API 模拟实现构建,不依赖于正在运行的容器。因此,有 与完整的端到端集成测试相比,存在一些差异 客户端和正在运行的实时服务器。spring-test

考虑这个问题的最简单方法是从空白开始。 无论你向它添加什么,请求都会变成什么。可能会让你大吃一惊的事情 是默认情况下没有上下文路径;没有cookie;无转发, 错误或异步调度;因此,没有实际的 JSP 渲染。相反 “转发”和“重定向”URL 保存在 和 满怀期待地断言。MockHttpServletRequestjsessionidMockHttpServletResponse

这意味着,如果使用 JSP,那么可以验证请求所针对的 JSP 页面 已转发,但未呈现 HTML。换言之,不会调用 JSP。注意 但是,所有其他不依赖于转发的渲染技术,例如 Thymeleaf 和 Freemarker 按预期将 HTML 呈现到响应正文。事实也是如此 用于通过方法呈现 JSON、XML 和其他格式。@ResponseBody

或者,您可以考虑从 带有 的 Spring Boot。请参阅 Spring Boot 参考指南@SpringBootTest

每种方法都有优点和缺点。Spring MVC Test 中提供的选项包括 从经典单元测试到完全集成测试,规模上的不同停止。成为 当然,Spring MVC Test 中没有一个选项属于经典单元的范畴 测试,但他们离它更近一些。例如,您可以隔离 Web 图层 通过将模拟服务注入控制器,在这种情况下,您正在测试 Web 层仅通过但具有实际的 Spring 配置,因为您 可能会独立于其上层测试数据访问层。此外,您还可以使用 独立设置,一次专注于一个控制器,并手动提供 使其工作所需的配置。DispatcherServlet

使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,这样的 测试是服务器端的,因此如果出现异常,您可以检查使用了哪个处理程序 使用 HandlerExceptionResolver 处理,模型的内容是什么,绑定什么 那里有错误,以及其他细节。这意味着写期望更容易, 因为服务器不是一个不透明的盒子,就像通过实际的 HTTP 测试它时一样 客户。这通常是经典单元测试的一个优点:它更容易编写, 原因和调试,但不能取代对完整集成测试的需求。在 同时,重要的是不要忽视这样一个事实,即响应是最 要检查的重要事项。简而言之,这里有多种风格和策略的空间 即使在同一个项目中进行测试。

更多例子

该框架自己的测试包括许多示例测试,旨在展示如何单独或通过 WebTestClient 使用 MockMvc。浏览这些示例以获取更多想法。

3.7.2. HtmlUnit 集成

Spring 提供了 MockMvcHtmlUnit 之间的集成。这简化了端到端测试的执行 使用基于 HTML 的视图时。通过此集成,您可以:

  • 使用 HtmlUnitWebDriverGeb 等工具轻松测试 HTML 页面,而无需 部署到 Servlet 容器。

  • 在页面中测试 JavaScript。

  • (可选)使用模拟服务进行测试以加快测试速度。

  • 在容器内端到端测试和容器外集成测试之间共享逻辑。

MockMvc 使用不依赖于 Servlet 容器的模板技术 (例如,Thymeleaf、FreeMarker 等),但它不适用于 JSP,因为 它们依赖于 Servlet 容器。
为什么选择 HtmlUnit 集成?

我想到的最明显的问题是“我为什么需要这个?答案是 最好通过探索一个非常基本的示例应用程序来找到。假设你有一个 Spring MVC Web 支持对对象执行 CRUD 操作的应用程序。该应用程序还 支持分页所有消息。你会如何去测试它?Message

使用 Spring MVC Test,我们可以很容易地测试我们是否能够创建一个 ,如下所示:Message

爪哇岛
Kotlin
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"));

如果我们想测试允许我们创建消息的表单视图,该怎么办?例如 假设我们的窗体类似于以下代码片段:

<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>

我们如何确保我们的表单产生正确的请求来创建新消息?一个 幼稚的尝试可能类似于以下内容:

爪哇岛
Kotlin
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());

该测试有一些明显的缺点。如果我们更新控制器以使用参数而不是 ,即使 HTML 表单,我们的表单测试也会继续通过 与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,因为 遵循:messagetext

爪哇岛
Kotlin
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"));

这将降低我们的测试错误通过的风险,但仍存在一些风险 问题:

  • 如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂: 字段类型正确?这些字段是否已启用?等等。

  • 另一个问题是,我们正在做我们预期的两倍的工作。我们必须首先 验证视图,然后使用刚才验证的相同参数提交视图。 理想情况下,这可以一次完成。

  • 最后,我们仍然无法解释一些事情。例如,如果表单有 我们也想测试 JavaScript 验证吗?

总体问题是,测试网页不涉及单个交互。 相反,它是用户如何与网页交互以及该网页如何交互的组合 页面与其他资源交互。例如,表单视图的结果用作 用户用于创建消息的输入。此外,我们的表单视图可能会 使用影响页面行为的其他资源,例如 JavaScript 验证。

集成测试来拯救?

为了解决前面提到的问题,我们可以执行端到端集成测试, 但这有一些缺点。考虑测试允许我们分页浏览 消息。我们可能需要以下测试:

  • 我们的页面是否向用户显示通知,以指示没有结果 当消息为空时可用?

  • 我们的页面是否正确显示一条消息?

  • 我们的页面是否正确支持分页?

要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致一些额外的挑战:

  • 确保正确的消息在数据库中可能很乏味。(考虑外键 约束。

  • 测试可能会变得很慢,因为每个测试都需要确保数据库位于 正确的状态。

  • 由于我们的数据库需要处于特定状态,因此我们不能并行运行测试。

  • 对自动生成的 ID、时间戳等项目执行断言可以 很难。

这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以减少端到端集成测试的数量 重构我们的详细测试,以使用运行速度更快、更可靠的模拟服务, 并且没有副作用。然后,我们可以实现少量真正的端到端 集成测试,用于验证简单的工作流程,以确保一切协同工作 适当地。

进入 HtmlUnit 集成

那么,我们如何才能在测试页面的交互和静止之间取得平衡 在我们的测试套件中保持良好的性能?答案是:“通过集成 MockMvc 替换为 HtmlUnit。

HtmlUnit 集成选项

当您想要将 MockMvc 与 HtmlUnit 集成时,您有多种选择:

  • MockMvc 和 HtmlUnit:如果 想要使用原始 HtmlUnit 库。

  • MockMvc 和 WebDriver:使用此选项可以 简化集成和端到端测试之间的开发并重用代码。

  • MockMvc 和 Geb:如果需要,请使用此选项 使用 Groovy 进行测试、简化开发,并在集成和 端到端测试。

MockMvc 和 HtmlUnit

本节介绍如何集成 MockMvc 和 HtmlUnit。如果需要,请使用此选项 以使用原始 HtmlUnit 库。

MockMvc 和 HtmlUnit 设置

首先,确保已包含对 的测试依赖关系。为了将 HtmlUnit 与 Apache HttpComponents 一起使用 4.5+,您需要使用 HtmlUnit 2.18 或更高版本。net.sourceforge.htmlunit:htmlunit

我们可以轻松地创建一个与 MockMvc 集成的 HtmlUnit,如下所示:WebClientMockMvcWebClientBuilder

爪哇岛
Kotlin
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
这是使用 .对于高级用法, 请参阅高级 MockMvcWebClientBuilderMockMvcWebClientBuilder

这确保了作为服务器引用的任何 URL 都定向到我们的实例,而无需真正的 HTTP 连接。任何其他 URL 是 像往常一样使用网络连接请求。这使我们能够轻松测试 CDN。localhostMockMvc

MockMvc 和 HtmlUnit 用法

现在我们可以像往常一样使用 HtmlUnit,但不需要部署我们的 应用程序复制到 Servlet 容器。例如,我们可以请求视图创建一个 消息包含以下内容:

爪哇岛
Kotlin
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
缺省上下文路径为 。或者,我们可以指定上下文路径, 如高级 MockMvcWebClientBuilder 中所述。""

一旦我们有了对 的引用,我们就可以填写表格并提交 创建消息,如以下示例所示:HtmlPage

爪哇岛
Kotlin
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();

最后,我们可以验证是否成功创建了新消息。以下 断言使用 AssertJ 库:

爪哇岛
Kotlin
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!");

前面的代码在许多方面改进了我们的 MockMvc 测试。 首先,我们不再需要显式验证我们的表单,然后创建一个请求 看起来像表格。取而代之的是,我们要求表格,填写并提交,从而 显著降低开销。

另一个重要因素是 HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们还可以测试 JavaScript 在我们页面中的行为。

请参阅 HtmlUnit 文档 有关使用 HtmlUnit 的其他信息。

高深MockMvcWebClientBuilder

在到目前为止的示例中,我们以最简单的方式使用 可能,通过构建一个基于为我们加载的 Spring TestContext 框架。以下示例中重复了此方法:MockMvcWebClientBuilderWebClientWebApplicationContext

爪哇岛
Kotlin
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}

我们还可以指定其他配置选项,如以下示例所示:

爪哇岛
Kotlin
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();
}

作为替代方案,我们可以通过单独配置实例并将其提供给 来执行完全相同的设置,如下所示:MockMvcMockMvcWebClientBuilder

爪哇岛
Kotlin
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();

这更冗长,但是,通过构建一个实例,我们有 MockMvc 的全部功能触手可及。WebClientMockMvc

有关创建实例的其他信息,请参阅设置选项MockMvc
MockMvc 和 WebDriver

在前面的章节中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API。在本节中,我们在 Selenium WebDriver 中使用了额外的抽象,使事情变得更加容易。

为什么选择 WebDriver 和 MockMvc?

我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver?这 Selenium WebDriver 提供了一个非常优雅的 API,让我们可以轻松组织代码。自 为了更好地展示它是如何工作的,我们在本节中探讨了一个示例。

尽管是 Selenium 的一部分,但 WebDriver 没有 需要 Selenium 服务器来运行测试。

假设我们需要确保正确创建消息。测试包括发现 HTML 表单输入元素,填写它们,并进行各种断言。

这种方法会产生许多单独的测试,因为我们希望测试错误条件 也。例如,我们希望确保如果我们只填写 形式。如果我们填写整个表单,则应显示新创建的消息 之后。

如果其中一个字段被命名为“summary”,我们可能会有类似于 在我们的测试中,以下在多个地方重复:

爪哇岛
Kotlin
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);

那么,如果我们将 更改为 会发生什么?这样做会迫使我们更新所有 我们的测试以纳入此更改。这违反了 DRY 原则,因此我们应该 理想情况下,将此代码提取到其自己的方法中,如下所示:idsmmry

爪哇岛
Kotlin
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);
}

这样做可以确保在更改 UI 时不必更新所有测试。

我们甚至可以更进一步,将这个逻辑放在一个 表示我们当前所处的位置,如以下示例所示:ObjectHtmlPage

爪哇岛
Kotlin
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());
    }
}

以前,这种模式称为页面对象模式。虽然我们 当然可以用 HtmlUnit 做到这一点,WebDriver 提供了一些工具,我们在 以下各节使此模式更易于实现。

MockMvc 和 WebDriver 设置

要将 Selenium WebDriver 与 Spring MVC 测试框架一起使用,请确保您的项目 包括对 的测试依赖关系。org.seleniumhq.selenium:selenium-htmlunit-driver

我们可以使用以下示例轻松创建与 MockMvc 集成的 Selenium WebDriver,如以下示例所示:MockMvcHtmlUnitDriverBuilder

爪哇岛
Kotlin
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
这是使用 .对于更高级 用法,请参阅高级 MockMvcHtmlUnitDriverBuilderMockMvcHtmlUnitDriverBuilder

前面的示例确保引用为服务器的任何 URL 都是 定向到我们的实例,而无需真正的 HTTP 连接。任何其他 像往常一样,使用网络连接请求 URL。这让我们可以轻松测试 CDN 的使用。localhostMockMvc

MockMvc 和 WebDriver 用法

现在,我们可以像往常一样使用 WebDriver,但不需要部署我们的 应用程序复制到 Servlet 容器。例如,我们可以请求视图创建一个 消息包含以下内容:

爪哇岛
Kotlin
CreateMessagePage page = CreateMessagePage.to(driver);

然后,我们可以填写表单并提交以创建消息,如下所示:

爪哇岛
Kotlin
ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);

这通过利用页面对象模式改进了 HtmlUnit 测试的设计。正如我们在为什么选择 WebDriver 和 MockMvc?中提到的,我们可以使用 Page 对象模式 使用 HtmlUnit,但使用 WebDriver 要容易得多。请考虑以下实现:CreateMessagePage

爪哇岛
Kotlin
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扩展 .我们不��述 的细节,但总而言之,它包含我们所有页面的通用功能。 例如,如果我们的应用程序具有导航栏、全局错误消息和其他 功能,我们可以将此逻辑放在共享位置。AbstractPageAbstractPage
2 对于我们所在的 HTML 页面的每个部分,我们都有一个成员变量 感兴趣。这些是类型。WebDriver 的 PageFactory 允许我们删除 通过自动解析 HtmlUnit 版本中的大量代码 每。PageFactory#initElements(WebDriver,Class<T>) 方法通过使用字段名称并查找它来自动解析每个字段 由 HTML 页面中元素的 或。WebElementCreateMessagePageWebElementWebElementidname
3 我们可以使用 @FindBy解来覆盖默认的查找行为。我们的示例展示了如何使用注释通过选择器 (input[type=submit]) 查找提交按钮。@FindBycss

最后,我们可以验证是否成功创建了新消息。以下 断言使用 AssertJ 断言库:

爪哇岛
Kotlin
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");

我们可以看到,我们的允许我们与自定义域模型进行交互。为 示例,它公开了一个返回对象的方法:ViewMessagePageMessage

爪哇岛
Kotlin
public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}

然后,我们可以在断言中使用丰富的域对象。

最后,我们不能忘记在测试完成后关闭实例, 如下:WebDriver

爪哇岛
Kotlin
@AfterEach
void destroy() {
    if (driver != null) {
        driver.close();
    }
}

有关使用 WebDriver 的其他信息,请参阅 Selenium WebDriver 文档

高深MockMvcHtmlUnitDriverBuilder

在到目前为止的示例中,我们以最简单的方式使用 可能,通过构建一个基于为我们加载的 Spring TestContext 框架。此处重复此方法,如下所示:MockMvcHtmlUnitDriverBuilderWebDriverWebApplicationContext

爪哇岛
Kotlin
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}

我们还可以指定其他配置选项,如下所示:

爪哇岛
Kotlin
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();
}

作为替代方案,我们可以通过单独配置实例并将其提供给 来执行完全相同的设置,如下所示:MockMvcMockMvcHtmlUnitDriverBuilder

爪哇岛
Kotlin
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();

这更冗长,但是,通过构建一个实例,我们有 MockMvc 的全部功能触手可及。WebDriverMockMvc

有关创建实例的其他信息,请参阅设置选项MockMvc
MockMvc 和 Geb

在上一节中,我们了解了如何将 MockMvc 与 WebDriver 一起使用。在本节中,我们 使用 Geb 使我们的测试更加 Groovy-er。

为什么选择 Geb 和 MockMvc?

Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 Web驱动程序。然而,Geb 通过处理一些 样板代码。

MockMvc 和 Geb 设置

我们可以轻松地使用使用 MockMvc 的 Selenium WebDriver 初始化 Geb,因为 遵循:Browser

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
这是使用 .对于更高级 用法,请参阅高级 MockMvcHtmlUnitDriverBuilderMockMvcHtmlUnitDriverBuilder

这确保了任何引用服务器的 URL 都定向到我们的实例,而无需真正的 HTTP 连接。任何其他 URL 是 通过正常使用网络连接请求。这使我们能够轻松测试 CDN。localhostMockMvc

MockMvc 和 Geb 用法

现在,我们可以像往常一样使用 Geb,但无需将应用程序部署到 一个 Servlet 容器。例如,我们可以请求视图使用 以后:

to CreateMessagePage

然后,我们可以填写表单并提交以创建消息,如下所示:

when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)

未找到的任何无法识别的方法调用或属性访问或引用都是 转发到当前页面对象。这删除了很多样板代码 直接使用 WebDriver 时需要。

与直接使用 WebDriver 一样,这通过使用 Page 对象改进了 HtmlUnit 测试的设计 模式。如前所述,我们可以将 Page 对象模式与 HtmlUnit 和 WebDriver,但使用 Geb 会更容易。考虑我们新的基于 Groovy 的实现:CreateMessagePage

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

我们的延伸 .我们不�一一��经评,但是,在 摘要,它包含我们所有页面的通用功能。我们定义了一个 URL,其中 可以找到此页面。这允许我们导航到该页面,如下所示:CreateMessagePagePagePage

to CreateMessagePage

我们还有一个闭包,用于确定我们是否在指定的页面。它应该 如果我们在正确的页面上,请返回。这就是为什么我们可以断言我们在 正确的页面,如下所示:attrue

then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用断言,以便我们可以确定哪里出了问题 如果我们在错误的页面上。

接下来,我们创建一个闭包,指定 页。我们可以使用 jQuery 式的导航器 API 来选择我们感兴趣的内容。content

最后,我们可以验证是否成功创建了新消息,如下所示:

then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

有关如何充分利用 Geb 的更多详细信息,请参阅 The Book of Geb 用户手册。

3.8. 测试客户端应用程序

您可以使用客户端测试来测试内部使用 .这 想法是声明预期的请求并提供“存根”响应,以便您可以 专注于单独测试代码(即,不运行服务器)。以下 示例演示如何执行此操作:RestTemplate

爪哇岛
Kotlin
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();

在前面的示例中,(客户端 REST 的中心类 tests) 配置一个自定义的 根据预期断言实际请求并返回“存根”响应。在这个 情况下,我们期望请求并希望返回包含内容的 200 响应。我们可以将其他预期请求和存根响应定义为 需要。当我们定义预期的请求和存根响应时,可以是 像往常一样在客户端代码中使用。在测试结束时,可以 用于验证是否满足所有期望。MockRestServiceServerRestTemplateClientHttpRequestFactory/greetingtext/plainRestTemplatemockServer.verify()

默认情况下,请求应按声明预期的顺序排列。你 可以在构建服务器时设置选项,在这种情况下,所有 检查期望(按顺序)以找到给定请求的匹配项。这意味着 允许以任何顺序提出请求。以下示例使用:ignoreExpectOrderignoreExpectOrder

爪哇岛
Kotlin
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();

即使默认情况下是无序请求,每个请求也只允许运行一次。 该方法提供了一个重载变体,该变体接受指定计数范围的参数(例如,、、、、等)。以下示例使用:expectExpectedCountoncemanyTimesmaxminbetweentimes

爪哇岛
Kotlin
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();

请注意,when 未设置(默认值),因此请求 应按声明顺序排列,则该顺序仅适用于任何 预期的请求。例如,如果“/something”应为两次,后跟 “/somewhere”三次,那么在有 对“/somewhere”的请求,但是,除了后面的“/something”和“/somewhere”之外, 请求可以随时提出。ignoreExpectOrder

作为上述所有方法的替代方法,客户端测试支持还提供了一个实现,您可以将其配置为 将其绑定到实例。这允许使用实际的服务器端处理请求 逻辑,但不运行服务器。以下示例演示如何执行此操作:ClientHttpRequestFactoryRestTemplateMockMvc

爪哇岛
Kotlin
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...

3.8.1. 静态导入

与服务器端测试一样,用于客户端测试的 Fluent API 需要一些静态 进口。通过搜索很容易找到这些。Eclipse 用户应将 和 添加为 “Java→编辑器”下的“Eclipse 首选项中的”收藏静态成员“→ Content 协助→收藏夹。这允许在键入 静态方法名称。其他 IDE(如 IntelliJ)可能不需要任何额外的 配置。检查是否支持对静态成员的代码完成。MockRest*MockRestRequestMatchers.*MockRestResponseCreators.*

3.8.2. 客户端REST测试的更多例子

Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。

4. 更多资源

有关测试的详细信息,请参阅以下资源:

  • JUnit:“一个程序员友好的 Java 测试框架”。 由 Spring Framework 在其测试套件中使用,并在 Spring TestContext Framework 中受支持。

  • TestNG:受 JUnit 启发的测试框架,增加了支持 用于测试组、数据驱动测试、分布式测试和其他功能。支持 在 Spring TestContext 框架

  • AssertJ: “Fluent 断言 for Java”, 包括对 Java 8 lambda、流和其他功能的支持。

  • Mock Objects:维基百科中的文章。

  • MockObjects.com:专门用于模拟对象的网站,一个 在测试驱动开发中改进代码设计的技术。

  • Mockito:基于 Test Spy 模式的 Java mock 库。由 Spring Framework 使用 在其测试套件中。

  • EasyMock:Java 库“为 接口(和通过类扩展的对象),通过使用 Java 的代理机制。

  • JMock:支持 Java 代码测试驱动开发的库 替换为模拟对象。

  • DbUnit:JUnit 扩展(也可用于 Ant 和 Maven),用于 针对数据库驱动的项目,除其他外,将您的数据库放入 测试运行之间的已知状态。

  • Testcontainers:支持 JUnit 的 Java 库 测试,提供通用数据库的轻量级、一次性实例,Selenium Web 浏览器,或任何其他可以在 Docker 容器中运行的东西。

  • Grinder:Java 负载测试框架。

  • SpringMockK:支持 Spring Boot 使用 MockK 而不是 Mockito 用 Kotlin 编写的集成测试。