测试

1. 弹簧测试简介

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

2. 单元测试

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

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

2.1. 模拟对象

Spring 包括许多专门用于模拟的包:spring-doc.cadn.net.cn

2.1.1. 环境

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

2.1.2. JNDI

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

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

2.1.3. Servlet API

org.springframework.mock.web包包含一套全面的 Servlet API 模拟对象,这些对象可用于测试 Web 上下文、控制器和过滤器。这些 模拟对象针对 Spring 的 Web MVC 框架,通常更多 比动态模拟对象(如EasyMock)使用方便 或替代的 Servlet API 模拟对象(例如 MockObjects)。spring-doc.cadn.net.cn

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

Spring MVC Test 框架基于模拟 Servlet API 对象构建,以提供 Spring MVC 的集成测试框架。参见 MockMvcspring-doc.cadn.net.cn

2.1.4. Spring Web 响应式

org.springframework.mock.http.server.reactive包包含模拟实现 之ServerHttpRequestServerHttpResponse用于 WebFlux 应用程序。这org.springframework.mock.web.server包包含一个模拟ServerWebExchange那 取决于那些模拟请求和响应对象。spring-doc.cadn.net.cn

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

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

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

2.2. 单元测试支持类

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

2.2.1. 通用测试实用程序

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

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

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

  • 纵容的 ORM 框架(例如 JPA 和 Hibernate)privateprotected田 访问而不是public域实体中属性的 setter 方法。spring-doc.cadn.net.cn

  • Spring 对注释的支持(例如@Autowired,@Inject@Resource), 提供依赖注入privateprotected字段、setter 方法、 和配置方法。spring-doc.cadn.net.cn

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

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

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

2.2.2. Spring MVC 测试实用程序

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

单元测试 Spring MVC 控制器
对 Spring MVC 进行单元测试Controller类作为 POJO,使用ModelAndViewAssert结合MockHttpServletRequest,MockHttpSession,依此类推,来自 Spring 的 Servlet API 模拟。为了对你的Spring MVC 和 REST 进行彻底的集成测试Controller与您的WebApplicationContextSpring MVC 的配置,请改用 Spring MVC 测试框架

3. 集成测试

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

3.1. 概述

能够执行一些集成测试而无需 部署到您的应用程序服务器或连接到其他企业基础架构。 这样做可以测试以下内容:spring-doc.cadn.net.cn

Spring Framework 为spring-test模块。实际 JAR 文件的名称可能包括发布版本 也可能在长期内org.springframework.test表格,取决于您从哪里获得 它来自(有关解释,请参阅依赖项管理部分)。该库包括org.springframework.testpackage,其中 包含用于与 Spring 容器集成测试的有价值的类。本次测试 不依赖于应用程序服务器或其他部署环境。此类测试是 运行速度比单元测试慢,但比等效的 Selenium 测试快得多,或者 依赖于部署到应用程序服务器的远程测试。spring-doc.cadn.net.cn

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

3.2. 集成测试的目标

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

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

3.2.1. 上下文管理和缓存

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

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

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

请参阅上下文管理和上下文缓存TestContext 框架。spring-doc.cadn.net.cn

3.2.2. 测试夹具的依赖注入

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

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

请参阅使用 TestContext 框架注入测试夹具的依赖项。spring-doc.cadn.net.cn

3.2.3. 事务管理

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

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

如果您希望提交一个事务(不寻常,但当您想要 特定测试来填充或修改数据库),您可以告诉 TestContext 框架,使用@Commit注解。spring-doc.cadn.net.cn

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

3.2.4. 集成测试的支持类

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

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

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

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

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

3.3. JDBC 测试支持

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

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

3.4. 注释

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

3.4.1. Spring 测试注释

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

Spring 的测试注解包括以下内容:spring-doc.cadn.net.cn

@BootstrapWith

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

@ContextConfiguration

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

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

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

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

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

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

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

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

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

以下示例同时使用位置和加载器:spring-doc.cadn.net.cn

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

请参阅上下文管理@Nested测试类配置@ContextConfigurationjavadocs 了解更多详情。spring-doc.cadn.net.cn

@WebAppConfiguration

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

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

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

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

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

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

@ContextHierarchy

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

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

如果您需要合并或覆盖给定级别的上下文层次结构,则必须通过提供相同的值来显式命名该级别name属性@ContextConfiguration在每个相应的级别。请参阅上下文层次结构@ContextHierarchyjavadoc更多示例。spring-doc.cadn.net.cn

@ActiveProfiles

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

以下示例表示dev配置文件应处于活动状态:spring-doc.cadn.net.cn

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

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

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

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

以下示例演示了如何从类路径声明属性文件:spring-doc.cadn.net.cn

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 test.properties在类路径的根目录中。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 test.properties在类路径的根目录中。

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

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

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

@DynamicPropertySource

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

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

Java
@ContextConfiguration
class MyIntegrationTests {

    static MyExternalServer server = // ...

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

    // tests ...
}
1 注释static方法@DynamicPropertySource.
2 接受DynamicPropertyRegistry作为论据。
3 注册动态server.port属性,以便从服务器延迟检索。
Kotlin
@ContextConfiguration
class MyIntegrationTests {

    companion object {

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

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

    // tests ...
}
1 注释static方法@DynamicPropertySource.
2 接受DynamicPropertyRegistry作为论据。
3 注册动态server.port属性,以便从服务器延迟检索。

有关更多详细信息,请参阅使用动态属性源进行上下文配置spring-doc.cadn.net.cn

@DirtiesContext

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

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

以下示例解释了上下文何时会因各种 配置场景:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

class ExtendedTests extends BaseTests {

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

class ExtendedTests : BaseTests() {

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

有关EXHAUSTIVECURRENT_LEVEL算法,请参阅DirtiesContext.HierarchyModejavadoc 的文档。spring-doc.cadn.net.cn

@TestExecutionListeners

@TestExecutionListeners用于注册特定测试类的侦听器,其 子类及其嵌套类。如果您希望在全球范围内注册监听器,您可以 应通过中描述的自动发现机制进行注册TestExecutionListener配置.spring-doc.cadn.net.cn

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

Java
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
1 寄存器二TestExecutionListener实现。
Kotlin
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
1 寄存器二TestExecutionListener实现。

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

@RecordApplicationEvents

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

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

@Commit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.4.2. 标准注释支持

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

JSR-250 生命周期注释

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

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

3.4.3. Spring JUnit 4 测试注释

仅当与 SpringRunnerSpring 的 JUnit 4规则Spring 的 JUnit 4 支持类结合使用时,才支持以下注解:spring-doc.cadn.net.cn

@IfProfileValue

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

您可以申请@IfProfileValue在类级别、方法级别或两者兼而有之。 类级用法@IfProfileValue优先于任何方法级用法 该类或其子类中的方法。具体而言,如果测试是 在类级别和方法级别启用。没有@IfProfileValue表示隐式启用测试。这类似于 JUnit 4 的语义@Ignore注释,但@Ignore始终禁用测试。spring-doc.cadn.net.cn

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

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

或者,您可以配置@IfProfileValue带有values(与OR语义)来实现对 JUnit 4 环境中测试组的类似 TestNG 的支持。 请考虑以下示例:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

@Repeat

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

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

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

3.4.4. Spring JUnit Jupiter 测试注释

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

@SpringJUnitConfig

@SpringJUnitConfig是一个组合的注释,它结合了@ExtendWith(SpringExtension.class)来自 JUnit Jupiter 与@ContextConfiguration从 Spring TestContext 框架。它可以在类级别用作插入式替换@ContextConfiguration. 关于配置选项,唯一的 区别@ContextConfiguration@SpringJUnitConfig是该组件类可以用value属性@SpringJUnitConfig.spring-doc.cadn.net.cn

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

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

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

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

请参阅上下文管理以及 javadoc@SpringJUnitConfig@ContextConfiguration了解更多详情。spring-doc.cadn.net.cn

@SpringJUnitWebConfig

@SpringJUnitWebConfig是一个组合的注释,它结合了@ExtendWith(SpringExtension.class)来自 JUnit Jupiter 与@ContextConfiguration@WebAppConfiguration来自 Spring TestContext 框架。您可以在课堂上使用它 水平作为直接替代品@ContextConfiguration@WebAppConfiguration. 关于配置选项,唯一的区别@ContextConfiguration@SpringJUnitWebConfig是您可以使用value属性@SpringJUnitWebConfig.此外,您可以覆盖value属性从@WebAppConfiguration仅通过使用resourcePath属性@SpringJUnitWebConfig.spring-doc.cadn.net.cn

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

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

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

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

@TestConstructor是类型级注释,用于配置参数 的测试类构造函数从测试的ApplicationContext.spring-doc.cadn.net.cn

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

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

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

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

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

从 Spring Framework 5.2 开始,@TestConstructor仅结合支持 使用SpringExtension与 JUnit Jupiter 一起使用。请注意,SpringExtension是 通常会自动为您注册 - 例如,在使用@SpringJUnitConfig@SpringJUnitWebConfig或来自 Spring Boot 测试。
@NestedTestConfiguration

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

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

更改缺省封闭配置继承模式

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

Spring TestContext 框架尊重@NestedTestConfiguration语义 后面的注释。spring-doc.cadn.net.cn

使用@NestedTestConfiguration通常只有结合起来才有意义 跟@NestedJUnit Jupiter 中的测试类;但是,可能还有其他测试 支持 Spring 的框架和利用此功能的嵌套测试类 注解。

@Nested测试类配置举个例子,进一步 详。spring-doc.cadn.net.cn

@EnabledIf

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

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

但是请注意,文本文字不是动态解析的结果 属性占位符的实用价值为零,因为@EnabledIf("false")是 相当于@Disabled@EnabledIf("true")在逻辑上毫无意义。spring-doc.cadn.net.cn

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

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

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

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

@DisabledIf

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

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

但是请注意,文本文字不是动态解析的结果 属性占位符的实用价值为零,因为@DisabledIf("true")是 相当于@Disabled@DisabledIf("false")在逻辑上毫无意义。spring-doc.cadn.net.cn

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

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

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

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

3.4.5. 测试的元注释支持

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

您可以将以下每一项用作元注释,并结合 TestContext 框架spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Java
@TransactionalDevTestConfig
class OrderRepositoryTests { }

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

@TransactionalDevTestConfig
class UserRepositoryTests { }

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

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

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

Java
@TransactionalIntegrationTest
void saveOrder() { }

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

@TransactionalIntegrationTest
fun deleteOrder() { }

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

3.5. Spring TestContext 框架

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

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

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

3.5.1. 关键抽象

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

TestContext

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

TestContextManager

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

TestExecutionListener

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

上下文加载器

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

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

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

  • DelegatingSmartContextLoader:两个默认加载器之一,它在内部委托给 一AnnotationConfigContextLoader一个GenericXmlContextLoaderGenericGroovyXmlContextLoader,取决于为test 类声明的配置,或者是否存在默认位置或默认配置类。仅当 Groovy 位于类路径上时,才会启用 Groovy 支持。spring-doc.cadn.net.cn

  • WebDelegatingSmartContextLoader:两个默认加载器之一,它在内部委托到AnnotationConfigWebContextLoader一个GenericXmlWebContextLoaderGenericGroovyXmlWebContextLoader,取决于声明的配置测试类或是否存在默认位置或默认配置 类。 一张网ContextLoader仅在以下情况下使用@WebAppConfiguration存在于test 类上。仅当 Groovy 位于类路径上时,才会启用 Groovy 支持。spring-doc.cadn.net.cn

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

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

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

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

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

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

3.5.2. 引导 TestContext 框架

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

TestContextBootstrapper定义用于引导 TestContext 框架的 SPI。一个TestContextBootstrapperTestContextManager加载TestExecutionListener实现,并构建TestContext它管理。您可以为 测试类(或测试类层次结构),使用@BootstrapWith,直接或作为 元注释。如果未使用@BootstrapWith,要么DefaultTestContextBootstrapperWebTestContextBootstrapper使用,具体取决于@WebAppConfiguration.spring-doc.cadn.net.cn

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

3.5.3.TestExecutionListener配置

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

注册TestExecutionListener实现

您可以注册TestExecutionListener测试类的显式实现,其子类及其嵌套类,使用@TestExecutionListeners注解。 请参阅注释支持和 javadoc@TestExecutionListeners了解详细信息和示例。spring-doc.cadn.net.cn

切换到默认值TestExecutionListener实现

如果扩展一个用@TestExecutionListeners并且你需要切换到使用默认的监听器集,你可以使用 以后。spring-doc.cadn.net.cn

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

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

具体来说,spring-test模块声明所有核心默认值TestExecutionListenerorg.springframework.test.context.TestExecutionListener键入 其META-INF/spring.factoriesproperties 文件。第三方框架和开发人员 可以贡献自己的TestExecutionListener实现到默认列表 听众通过自己的方式META-INF/spring.factories性能 文件。spring-doc.cadn.net.cn

订购TestExecutionListener实现

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

合并TestExecutionListener实现

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

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

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

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

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

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

3.5.4. 应用程序事件

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

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

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

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

    @Autowired
    OrderService orderService;

    @Autowired
    ApplicationEvents events; (2)

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

    @Autowired
    lateinit var orderService: OrderService

    @Autowired
    lateinit var events: ApplicationEvents (2)

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

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

3.5.5. 测试执行事件

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

这些事件可能出于各种原因被使用,例如重置模拟 Bean 或跟踪 测试执行。使用测试执行事件而不是实现测试执行事件的一个优点 一个习惯TestExecutionListener测试执行事件可以被任何 在测试中注册的春豆ApplicationContext,这些Beans可能会受益 直接来自依赖注入和ApplicationContext.在 对比,一个TestExecutionListener不是ApplicationContext.spring-doc.cadn.net.cn

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

因此,一个BeforeTestClassEvent直到ApplicationContext已被另一个人加载TestExecutionListener.例如,使用 默认的TestExecutionListener已注册的实现,一个BeforeTestClassEvent不会为使用 特殊测试ApplicationContext,但是一个BeforeTestClassEvent 发布 同一测试套件中使用相同测试的任何后续测试类ApplicationContext因为上下文在后续测试时已经加载 类运行(只要上下文尚未从ContextCache通过@DirtiesContext或最大大小逐出策略)。spring-doc.cadn.net.cn

如果您希望确保BeforeTestClassEvent始终为每次测试发布 类,您需要注册一个TestExecutionListener加载ApplicationContextbeforeTestClass回调,并且TestExecutionListener必须EventPublishingTestExecutionListener.spring-doc.cadn.net.cn

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

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

异常处理

默认情况下,如果测试执行事件监听器在使用 事件,则该异常将传播到正在使用的底层测试框架(例如 JUnit 或 TestNG)。例如,如果消耗BeforeTestMethodEvent结果 异常,相应的测试方法将因异常而失败。在 相反,如果异步测试执行事件监听器抛出异常,则 异常不会传播到底层测试框架。有关 异步异常处理,请参阅类级 Javadoc@EventListener.spring-doc.cadn.net.cn

异步侦听器

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

3.5.6. 上下文管理

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

@Autowired ApplicationContext

作为实现ApplicationContextAware接口,您可以注入通过@Autowired注解字段或 setter 方法,如以下示例所示:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    ApplicationContext applicationContext;

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

    @Autowired (1)
    lateinit var applicationContext: ApplicationContext

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

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

Java
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    WebApplicationContext wac;

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

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

使用@AutowiredDependencyInjectionTestExecutionListener,默认配置(请参阅测试夹具的依赖注入)。spring-doc.cadn.net.cn

使用 TestContext 框架的测试类不需要扩展任何特定的类或实现特定接口来配置其应用程序上下文。 相反 配置是通过声明@ContextConfiguration注释class 级别。如果您的测试类没有显式声明应用程序上下文资源locations 或组件类,则配置的ContextLoader确定如何加载上下文,从默认位置或默认配置类。除了上下文资源位置和组件类,还可以配置应用程序上下文通过应用程序上下文初始值设定项。spring-doc.cadn.net.cn

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

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

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

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

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

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

如果同时省略locationsvalue属性@ContextConfiguration注释,TestContext 框架会尝试检测默认的 XML 资源位置。具体说来GenericXmlContextLoaderGenericXmlWebContextLoader根据测试名称检测默认位置 类。如果您的类名为com.example.MyTest,GenericXmlContextLoader加载您的 应用程序上下文"classpath:com/example/MyTest-context.xml".以下内容 示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

您可以使用以下命令同时声明 XML 配置文件和 Groovy 脚本 这locationsvalue属性@ContextConfiguration.如果 配置的资源位置以.xml,则使用XmlBeanDefinitionReader.否则,它将使用GroovyBeanDefinitionReader.spring-doc.cadn.net.cn

以下列表显示了如何在集成测试中组合两者:spring-doc.cadn.net.cn

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

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

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

术语“组件类”可以指以下任何一项:spring-doc.cadn.net.cn

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

如果您省略classes属性@ContextConfiguration注释,则 TestContext 框架尝试检测默认配置类的存在。 具体说来AnnotationConfigContextLoaderAnnotationConfigWebContextLoader全部检测static测试类的嵌套类,满足 配置类实现,如@Configurationjavadoc 的文档。 请注意,配置类的名称是任意的。此外,测试类可以 包含多个static嵌套配置类(如果需要)。在以下内容中 示例,OrderServiceTest类声明一个static嵌套配置类 叫Config自动用于加载ApplicationContext用于测试 类:spring-doc.cadn.net.cn

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

    @Configuration
    static class Config {

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

    @Autowired
    OrderService orderService;

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

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

    @Autowired
    lateinit var orderService: OrderService

    @Configuration
    class Config {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

同样,在下一个使用组件类的示例中,ApplicationContextExtendedTestBaseConfigExtendedConfig类,在 次序。在ExtendedConfig因此,可以覆盖(即替换) 中定义的BaseConfig. 以下示例显示了一个类如何扩展另一个类,并同时使用自己的配置类和超类的配置类:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

您可以使用@ActiveProfilesSmartContextLoaderSPI 的@ActiveProfiles不支持旧版的实现ContextLoaderSPI.

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

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

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

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

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

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

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

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

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

    @Autowired
    TransferService transferService;

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

    @Autowired
    lateinit var transferService: TransferService

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

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

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

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

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

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

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

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

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

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

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

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

    @Autowired DataSource dataSource;

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

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

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

    @Autowired
    lateinit var dataSource: DataSource

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

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

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

    @Autowired
    TransferService transferService;

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

    @Autowired
    lateinit var transferService: TransferService

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

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

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

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

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

    @Autowired
    TransferService transferService;

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

    @Autowired
    lateinit var transferService: TransferService

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

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

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

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

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

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

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

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

Spring Framework 对具有 属性源的层次结构,并且您可以使用特定于测试的集成测试来配置集成测试 属性来源。与@PropertySource用于@Configuration类,您可以声明@TestPropertySource测试上的注释 class 来声明测试属性文件或内联属性的资源位置。 这些测试属性源将添加到PropertySourcesEnvironment对于ApplicationContext加载以进行带注释的集成测试。spring-doc.cadn.net.cn

您可以使用@TestPropertySourceSmartContextLoaderSPI 的@TestPropertySource不支持旧版的实现ContextLoaderSPI.spring-doc.cadn.net.cn

实现SmartContextLoader访问合并的测试属性源值通过getPropertySourceLocations()getPropertySourceProperties()方法MergedContextConfiguration.spring-doc.cadn.net.cn

声明测试属性源

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

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

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

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

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

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

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

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

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

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

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

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

默认属性文件检测

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

优先

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

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

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

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

如果inheritLocationsinheritProperties属性@TestPropertySource是 设置为false,分别是测试类的位置或内联属性 shadow 并有效地替换超类定义的配置。spring-doc.cadn.net.cn

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

在下一个示例中,ApplicationContextBaseTest仅使用base.properties文件作为测试属性源。相比之下,ApplicationContextExtendedTest通过使用base.propertiesextended.properties文件作为测试属性源位置。以下示例演示如何定义 使用properties文件:spring-doc.cadn.net.cn

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

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

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

在下一个示例中,ApplicationContextBaseTest仅使用 内联key1财产。相比之下,ApplicationContextExtendedTest是 使用内联key1key2性能。以下示例演示了如何 要使用内联属性定义子类及其超类中的属性:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

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

    // tests ...

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

    companion object {

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

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

    // tests ...

}
优先

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

加载一个WebApplicationContext

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

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

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

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

Java
@ExtendWith(SpringExtension.class)

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

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

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

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

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

以下示例演示如何使用@WebAppConfiguration以及 XML 资源位置@ContextConfiguration:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)

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

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

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

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

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

以下示例显示,我们可以重写两者的默认资源语义 通过指定 Spring 资源前缀来注释:spring-doc.cadn.net.cn

Java
@ExtendWith(SpringExtension.class)

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

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

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

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

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

使用Web模拟

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

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

Java
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

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

    @Autowired
    lateinit var wac: WebApplicationContext // cached

    @Autowired
    lateinit var servletContext: MockServletContext // cached

    @Autowired
    lateinit var session: MockHttpSession

    @Autowired
    lateinit var request: MockHttpServletRequest

    @Autowired
    lateinit var response: MockHttpServletResponse

    @Autowired
    lateinit var webRequest: ServletWebRequest

    //...
}
上下文缓存

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

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

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

测试套件和分叉进程

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

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

上下文缓存的大小以默认最大大小 32 为限。每当 达到最大大小时,使用最近最少使用的 (LRU) 逐出策略来逐出和 关闭过时的上下文。您可以从命令行或构建中配置最大大小 脚本,通过设置名为spring.test.context.cache.maxSize.作为 或者,您可以通过SpringProperties机制。spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

上下文层次结构

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

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

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

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

ControllerIntegrationTests表示 Spring MVC Web 应用程序通过声明由两个级别组成的上下文层次结构, 一个用于根WebApplicationContext(通过使用TestAppConfig @Configuration类),一个用于调度程序 servletWebApplicationContext(通过使用WebConfig @Configuration类)。这WebApplicationContext自动连接到测试实例的 is 是子上下文的 (即 层次结构中的最低上下文)。以下列表显示了此配置方案:spring-doc.cadn.net.cn

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

    @Autowired
    WebApplicationContext wac;

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

    @Autowired
    lateinit var wac: WebApplicationContext

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

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

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

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

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

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

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

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

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

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

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

与前面的示例相比,此示例演示了如何覆盖 通过设置inheritLocations标记@ContextConfigurationfalse.因此, 应用程序上下文ExtendedTests仅从/test-user-config.xml和 将其父级设置为从以下位置加载的上下文/app-config.xml.以下列表 显示了以下配置方案:spring-doc.cadn.net.cn

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

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

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

3.5.7. 测试夹具的依赖注入

当您使用DependencyInjectionTestExecutionListener(由默认配置),测试实例的依赖项是从应用程序上下文中注入的应用程序上下文@ContextConfiguration或相关 附注。 您可以使用 setter 注入、字段注入或两者兼而有之,具体取决于您选择哪些注释以及是否将它们放在 setter 方法或字段上。如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入(请参阅依赖注入SpringExtension). 为了与 Spring 基于注释的注入支持保持一致,您还可以使用 Spring 的@Autowired注释或@InjectJSR-330 的字段和 setter 注入的注释。spring-doc.cadn.net.cn

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

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

如果您不希望将依赖项注入应用于测试实例,请不要注释 fields 或 setter 方法与@Autowired@Inject.或者,您可以禁用 通过显式配置您的类来完全注入依赖项@TestExecutionListeners并省略DependencyInjectionTestExecutionListener.class从监听器列表中。spring-doc.cadn.net.cn

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

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

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

第一个代码列表显示了基于 JUnit Jupiter 的测试类实现,该测试类 使用@Autowired用于现场注射:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

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

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

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

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

前面的代码列表使用与@ContextConfiguration注释(即,repository-config.xml).以下内容 显示此配置:spring-doc.cadn.net.cn

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

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

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

</beans>

如果您从 Spring 提供的测试基类扩展,该基类恰好使用@Autowired在其 setter 方法之一上,您可能有多个受影响的 bean 类型(例如,多个DataSource豆子)。在 在这种情况下,您可以覆盖 setter 方法并使用@Qualifier注释到 指示特定的目标 bean,如下所示(但请确保委托给被覆盖的 方法也一样):spring-doc.cadn.net.cn

Java
// ...

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

// ...
Kotlin
// ...

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

// ...

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

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

Spring 支持请求范围和会话范围 bean 的 bean,你可以测试你的请求范围和会话范围 bean 的 bean,请按照以下步骤作:spring-doc.cadn.net.cn

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

请求范围的 Bean 配置
<beans>

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

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

</beans>

RequestScopedBeanTests,我们将UserService(即,下面的主语 test)和MockHttpServletRequest到我们的测试实例中。在我们的requestScope()test 方法,我们通过在 提供的MockHttpServletRequest.当loginUser()方法被调用在我们的userService,我们确信用户服务可以访问请求范围的loginAction对于当前MockHttpServletRequest(也就是说,我们只是 设置参数)。然后,我们可以根据已知的 输入用户名和密码。以下列表显示了如何执行此作:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

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

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

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

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

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

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

会话范围的 Bean 配置
<beans>

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

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

</beans>

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

Java
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

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

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

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

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

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

3.5.9. 事务管理

在 TestContext 框架中,事务由TransactionalTestExecutionListener,默认情况下配置,即使您没有 显式声明@TestExecutionListeners在测试类上。启用对 事务,但是,您必须配置PlatformTransactionManagerbean 中的ApplicationContext加载了@ContextConfiguration语义(进一步 详细信息将在后面提供)。此外,您必须声明 Spring 的@Transactional在类或方法级别进行测试的注释。spring-doc.cadn.net.cn

测试管理的事务

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

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

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

具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过 一个java.lang.ThreadLocal变量)在调用当前测试方法之前。如果测试框架在新线程中调用当前测试方法以支持抢占超时,则在当前测试方法中执行的任何作都不会在测试管理的事务中调用。因此,任何此类作的结果不会随测试管理的事务一起回滚。相反,此类作将提交到持久存储(例如关系数据库),即使测试管理的事务已被 Spring 正确回滚。spring-doc.cadn.net.cn

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

启用和禁用事务

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

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

valuetransactionManagerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

propagationspring-doc.cadn.net.cn

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

isolationspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

timeoutspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

readOnlyspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

rollbackForrollbackForClassNamespring-doc.cadn.net.cn

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

noRollbackFornoRollbackForClassNamespring-doc.cadn.net.cn

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

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

如果您需要在 transaction,不妨注入一个对应的PlatformTransactionManager到 测试类,然后将其与TransactionTemplate用于程序化 事务管理。spring-doc.cadn.net.cn

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

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

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

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

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

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

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

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

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

    @Autowired
    lateinit var repository: HibernateUserRepository

    @Autowired
    lateinit var sessionFactory: SessionFactory

    lateinit var jdbcTemplate: JdbcTemplate

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

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

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

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

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

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

事务回滚和提交行为中所述,无需 清理createUser()方法运行,因为对 数据库由TransactionalTestExecutionListener.spring-doc.cadn.net.cn

事务回滚和提交行为

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

程序化事务管理

您可以使用静态 方法TestTransaction.例如,您可以使用TestTransaction测试中 方法、before methods 和 after 方法来启动或结束当前测试管理 事务或配置当前测试管理事务以进行回滚或提交。 对TestTransaction只要TransactionalTestExecutionListener已启用。spring-doc.cadn.net.cn

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

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

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

        deleteFromTables("user");

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

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

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

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

        deleteFromTables("user")

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

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

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

有时,可能需要在事务测试之前或之后运行某些代码 方法,但要在事务上下文之外——例如,验证初始 运行测试之前的数据库状态或验证预期的事务提交 测试运行后的行为(如果测试配置为提交事务)。TransactionalTestExecutionListener支持@BeforeTransaction@AfterTransaction注解。您可以注释任何void测试类中的方法或任何void默认方法,在测试接口中使用其中之一 注释,以及TransactionalTestExecutionListener确保您在 transaction 方法或 after transaction 方法在适当的时间运行。spring-doc.cadn.net.cn

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

TransactionalTestExecutionListener期望PlatformTransactionManager豆子要成为 在 Spring 中定义ApplicationContext用于测试。如果有多个实例 之PlatformTransactionManager在测试的ApplicationContext,您可以声明一个 限定符,使用@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer可由@Configuration类。请查阅Java文档 为TestContextTransactionUtils.retrieveTransactionManager()有关 用于在测试的ApplicationContext.spring-doc.cadn.net.cn

所有与事务相关的注释的演示

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

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

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

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

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

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

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

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

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

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

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

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

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

}
测试 ORM 代码时避免误报

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

Java
// ...

@Autowired
SessionFactory sessionFactory;

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

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

// ...
Kotlin
// ...

@Autowired
lateinit var sessionFactory: SessionFactory

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

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

// ...

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

Java
// ...

@PersistenceContext
EntityManager entityManager;

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

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

// ...
Kotlin
// ...

@PersistenceContext
lateinit var entityManager:EntityManager

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

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

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

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

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

以下示例演示如何刷新EntityManager以确保@PostPersist当实体被持久化时,会调用回调。具有 一个@PostPersistcallback 方法已为Person实体用于 例。spring-doc.cadn.net.cn

Java
// ...

@Autowired
JpaPersonRepository repo;

@PersistenceContext
EntityManager entityManager;

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

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

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

// ...
Kotlin
// ...

@Autowired
lateinit var repo: JpaPersonRepository

@PersistenceContext
lateinit var entityManager: EntityManager

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

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

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

// ...

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

3.5.10. 执行 SQL 脚本

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

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

以编程方式执行 SQL 脚本

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

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

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

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

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

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

除了上述以编程方式运行 SQL 脚本的机制之外, 您可以在 Spring TestContext 框架中以声明方式配置 SQL 脚本。 具体来说,您可以声明@Sql将测试类或测试方法上的注释设置为 配置单个 SQL 语句或 SQL 脚本的资源路径,这些 在集成测试方法之前或之后针对给定数据库运行。对@SqlSqlScriptsTestExecutionListener,默认启用。spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

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

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

声明多个@Sql

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

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

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

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

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

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

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

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

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

脚本配置@SqlConfig

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

提供的配置选项@Sql@SqlConfig等同于那些 基金资助ScriptUtilsResourceDatabasePopulator但是这些的超集 由<jdbc:initialize-database/>XML 命名空间元素。请参阅 各个属性@Sql@SqlConfig了解详情。spring-doc.cadn.net.cn

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

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

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

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

    final JdbcTemplate jdbcTemplate;

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

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

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

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

    val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)

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

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

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

请注意,在usersTest()方法是 run,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sqlscript)由TransactionalTestExecutionListener(请参阅事务管理 细节)。spring-doc.cadn.net.cn

合并和覆盖配置@SqlMergeMode

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

3.5.11. 并行测试执行

Spring Framework 5.0 引入了对在单个 JVM 中使用 Spring TestContext 框架时并行执行测试的基本支持。一般来说,这意味着大多数测试类或测试方法可以并行运行,而无需对测试代码进行任何更改或配置。spring-doc.cadn.net.cn

如需详细了解如何设置并行测试执行,请参阅testing 框架、构建工具或 IDE 的文档。

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

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

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

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

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

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

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

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

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

3.5.12. TestContext 框架支持类

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

弹簧 JUnit 4 流轮

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

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

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

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

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

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

Spring JUnit 4 规则

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

SpringClassRule是 JUnitTestRule支持 Spring 的类级功能 TestContext 框架,而SpringMethodRule是 JUnitMethodRule支持 Spring TestContext 框架的实例级和方法级特性。spring-doc.cadn.net.cn

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

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

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

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

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

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

    @Rule
    val springMethodRule = SpringMethodRule()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

依赖注入SpringExtension

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

具体说来SpringExtension可以从测试的ApplicationContext转换为测试构造函数和方法,这些构造函数和方法被@BeforeAll,@AfterAll,@BeforeEach,@AfterEach,@Test,@RepeatedTest,@ParameterizedTest,等。spring-doc.cadn.net.cn

构造函数注入

如果 JUnit Jupiter 测试类的构造函数中的特定参数类型为ApplicationContext(或其子类型)或被注释或元注释@Autowired,@Qualifier@Value,Spring 注入该特定 参数与相应的 bean 或测试的ApplicationContext.spring-doc.cadn.net.cn

如果出现以下情况,还可以将 Spring 配置为自动连接测试类构造函数的所有参数 构造函数被认为是可自动连接的。构造函数被视为 如果满足以下条件之一(按优先级排序),则可自动切换。spring-doc.cadn.net.cn

@TestConstructor有关使用的详细信息@TestConstructor以及如何更改全局测试构造函数自动连线模式spring-doc.cadn.net.cn

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

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

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

使用@DirtiesContext在“测试方法之前”或“测试方法之后”模式下 结合@TestInstance(PER_CLASS),必须从 Spring 配置依赖项 通过现场或入场器进样供给,以便在测试之间重新进样 方法调用。spring-doc.cadn.net.cn

在下面的示例中,Spring 注入了OrderServicebean 来自ApplicationContext加载自TestConfig.class进入OrderServiceIntegrationTests构造 函数。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

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

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

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

如果spring.test.constructor.autowire.mode属性是all(参见@TestConstructor),我们可以省略@Autowired在上一个示例中的构造函数上,结果如下。spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

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

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

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

在下面的示例中,Spring 注入了OrderServiceApplicationContext加载自TestConfig.class进入deleteOrder()测试方法:spring-doc.cadn.net.cn

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

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

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

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

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

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

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

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

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

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

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

@Nested测试类配置

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

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

为了允许开发团队将默认值更改为OVERRIDE–例如 为了与 Spring Framework 5.0 到 5.2 兼容 – 可以更改默认模式 通过 JVM 系统属性或spring.properties文件在 类路径。请参阅“更改 默认封闭配置继承模式“注意spring-doc.cadn.net.cn

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

Java
@SpringJUnitConfig(TestConfig.class)
class GreetingServiceTests {

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

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

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

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

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

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

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

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

org.springframework.test.context.testngpackage 提供以下支持 基于 TestNG 的测试用例的类:spring-doc.cadn.net.cn

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

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

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

3.6. Web测试客户端

WebTestClient是专为测试服务器应用程序而设计的 HTTP 客户端。它包裹着 Spring 的 WebClient 并使用它来执行请求 但暴露了用于验证响应的测试外观。WebTestClient可用于 执行端到端 HTTP 测试。它还可用于测试 Spring MVC 和 Spring WebFlux 没有运行服务器的应用程序通过模拟服务器请求和响应对象。spring-doc.cadn.net.cn

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

3.6.1. 设置

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

绑定到控制器

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

对于 WebFlux 应用程序,请使用以下内容加载与 WebFlux Java 配置等效的基础设施,注册给定的 controller(s),并创建一个 WebHandler 链来处理请求:spring-doc.cadn.net.cn

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

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

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

此设置允许您使用 Spring MVC 或 Spring WebFlux 加载 Spring 配置基础设施和控制器声明,并使用它通过模拟请求处理请求和响应对象,而无需运行服务器。spring-doc.cadn.net.cn

对于 WebFlux,请在 SpringApplicationContext传递给 WebHttpHandlerBuilder 以创建要处理的 WebHandler 链 请求:spring-doc.cadn.net.cn

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

    WebTestClient client;

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

    lateinit var client: WebTestClient

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

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

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

    @Autowired
    WebApplicationContext wac; (2)

    WebTestClient client;

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

    @Autowired
    lateinit var wac: WebApplicationContext; (2)

    lateinit var client: WebTestClient

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

此设置允许您通过 模拟请求和响应对象,无需运行服务器。spring-doc.cadn.net.cn

对于 WebFlux,请使用以下委托RouterFunctions.toWebHandler自 创建服务器设置来处理请求:spring-doc.cadn.net.cn

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

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

绑定到服务器

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

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

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

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

3.6.2. 编写测试

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

要使用 JSONAssert 验证完整的 JSON 内容,请执行以下作:spring-doc.cadn.net.cn

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

要使用 JSONPath 验证 JSON 内容,请执行以下作:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

WebTestClient是 HTTP 客户端,因此它只能验证客户端中的内容 响应,包括状态、标头和正文。spring-doc.cadn.net.cn

当使用 MockMvc 服务器设置测试 Spring MVC 应用程序时,您拥有额外的 选择对服务器响应执行进一步的断言。要做到这一点,首先要 获取ExchangeResult断言身体后:spring-doc.cadn.net.cn

Java
// For a response with a body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();
Kotlin
// For a response with a body
val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
val result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();

然后切换到 MockMvc 服务器响应断言:spring-doc.cadn.net.cn

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

3.7. 模拟Mvc

Spring MVC Test 框架,也称为 MockMvc,为测试 Spring 提供了支持 MVC 应用程序。它执行完整的 Spring MVC 请求处理,但通过模拟请求和 response 对象,而不是正在运行的服务器。spring-doc.cadn.net.cn

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

3.7.1. 概述

您可以通过实例化控制器、注入它具有依赖项并调用其方法来为 Spring MVC 编写普通单元测试。但是,此类测试不会验证请求映射、数据绑定、消息转换、类型转换、验证和它们是否涉及任何支持@InitBinder,@ModelAttribute@ExceptionHandler方法。spring-doc.cadn.net.cn

Spring MVC 测试框架,也称为MockMvc,旨在提供更完整的 测试没有正在运行的服务器的 Spring MVC 控制器。它通过调用 这DispatcherServletspring-test模块,该模块复制了完整的 Spring MVC 请求处理,而无需 正在运行的服务器。spring-doc.cadn.net.cn

MockMvc 是一个服务器端测试框架,可让您验证大部分功能 使用轻量级和有针对性的测试的 Spring MVC 应用程序。你可以在 它自己的来执行请求和验证响应,或者您也可以通过 将 MockMvc 插入为 Server,用于处理请求的 WebTestClient API 跟。spring-doc.cadn.net.cn

静态导入

直接使用 MockMvc 执行请求时,您需要静态导入:spring-doc.cadn.net.cn

记住这一点的一种简单方法是搜索MockMvc*.如果使用 Eclipse,请务必同时 在 Eclipse 首选项中将上述内容添加为“最喜欢的静态成员”。spring-doc.cadn.net.cn

通过 WebTestClient 使用 MockMvc 时,不需要静态导入。 这WebTestClient提供流畅的 API,无需静态导入。spring-doc.cadn.net.cn

设置选择

MockMvc 可以通过以下两种方式之一进行设置。一种是直接指向控制器 想要测试并以编程方式配置 Spring MVC 基础设施。二是 指向包含 Spring MVC 和控制器基础设施的 Spring 配置。spring-doc.cadn.net.cn

要设置 MockMvc 以测试特定控制器,请使用以下命令:spring-doc.cadn.net.cn

Java
class MyWebTests {

    MockMvc mockMvc;

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

    // ...

}
Kotlin
class MyWebTests {

    lateinit var mockMvc : MockMvc

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }

    // ...

}

或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该 WebTestClient 委托给同一构建器如上所示。spring-doc.cadn.net.cn

要通过 Spring 配置设置 MockMvc,请使用以下命令:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {

    MockMvc mockMvc;

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

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {

    lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

或者,您也可以在通过 WebTestClient 进行测试时使用此设置,该 WebTestClient 委托给同一构建器如上所示。spring-doc.cadn.net.cn

您应该使用哪个设置选项?spring-doc.cadn.net.cn

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

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

然后,您可以将模拟服务注入到测试中,以设置和验证您的 期望值,如以下示例所示:spring-doc.cadn.net.cn

Java
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {

    @Autowired
    AccountService accountService;

    MockMvc mockMvc;

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

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {

    @Autowired
    lateinit var accountService: AccountService

    lateinit mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

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

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

设置功能

无论您使用哪个 MockMvc 构建器,所有MockMvcBuilder实现提供 一些常见且非常有用的功能。例如,您可以声明Accept标题 所有请求,并期望状态为 200,以及Content-Type标题 响应,如下所示:spring-doc.cadn.net.cn

Java
// static import of MockMvcBuilders.standaloneSetup

MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

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

Java
// static import of SharedHttpSessionConfigurer.sharedHttpSession

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

// Use mockMvc to perform requests...
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

请参阅 javadocConfigurableMockMvcBuilder获取所有 MockMvc 构建器功能的列表,或使用 IDE 探索可用选项。spring-doc.cadn.net.cn

执行请求

本节介绍如何单独使用 MockMvc 来执行请求和验证响应。如果通过WebTestClient请参阅有关编写测试的相应部分。spring-doc.cadn.net.cn

要执行使用任何 HTTP 方法的请求,如以下示例所示:spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.*

mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
Kotlin
import org.springframework.test.web.servlet.post

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

您还可以执行内部使用MockMultipartHttpServletRequest这样就不会对多部分进行实际解析 请求。相反,您必须将其设置为类似于以下示例:spring-doc.cadn.net.cn

Java
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
Kotlin
import org.springframework.test.web.servlet.multipart

mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}

可以在 URI 模板样式中指定查询参数,如以下示例所示:spring-doc.cadn.net.cn

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

您还可以添加表示查询或表单的 Servlet 请求参数 参数,如以下示例所示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/hotels") {
    param("thing", "somewhere")
}

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

在大多数情况下,最好将上下文路径和 Servlet 路径留在 请求 URI。如果必须使用完整的请求 URI 进行测试,请务必将contextPathservletPath相应地,以便请求映射正常工作,如以下示例所示 显示:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}

在前面的示例中,设置contextPathservletPath每个执行的请求。相反,您可以设置默认请求 属性,如以下示例所示:spring-doc.cadn.net.cn

Java
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

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

定义期望

您可以通过附加一个或多个期望来定义期望andExpect(..)调用后 执行请求,如以下示例所示。一旦一个期望落空, 不会断言其他期望。spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/accounts/1").andExpect {
    status { isOk() }
}

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

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

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

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

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

第二类断言超出了回应范围。这些断言可以让您 检查 Spring MVC 的特定方面,例如哪个控制器方法处理了 请求,是否引发并处理了异常,模型的内容是什么, 选择了什么视图,添加了哪些 Flash 属性,等等。他们还允许您 检查 Servlet 的特定方面,例如请求和会话属性。spring-doc.cadn.net.cn

以下测试断言绑定或验证失败:spring-doc.cadn.net.cn

Java
mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andExpect {
    status { isOk() }
    model {
        attributeHasErrors("person")
    }
}

很多时候,在编写测试时,转储执行的结果很有用 请求。您可以按如下方式执行此作,其中print()是从MockMvcResultHandlers:spring-doc.cadn.net.cn

Java
mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status { isOk() }
        model {
            attributeHasErrors("person")
        }
    }

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

在某些情况下,您可能希望直接访问结果并验证以下内容 否则无法验证。这可以通过将.andReturn()毕竟 其他期望,如以下示例所示:spring-doc.cadn.net.cn

Java
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
Kotlin
var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...

如果所有测试都重复相同的期望值,则可以在以下情况下设置一次共同期望值。 构建MockMvc实例,如以下示例所示:spring-doc.cadn.net.cn

Java
standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

请注意,通用期望始终是应用的,如果没有 创建单独的MockMvc实例。spring-doc.cadn.net.cn

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

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

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

Java
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Kotlin
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}
异步请求

本节介绍如何单独使用 MockMvc 来测试异步请求处理。 如果通过 WebTestClient 使用 MockMvc,则无需执行任何特殊作即可 异步请求作为WebTestClient自动执行描述的内容 在本节中。spring-doc.cadn.net.cn

Spring MVC 支持的 Servlet 3.0 异步请求通过退出 Servlet 容器来工作 线程,并允许应用程序异步计算响应,之后 进行异步调度以完成 Servlet 容器线程上的处理。spring-doc.cadn.net.cn

在 Spring MVC Test 中,可以通过断言生成的异步值来测试异步请求 首先,然后手动执行异步调度,最后验证响应。 下面是返回DeferredResult,Callable, 或反应型,如反应器Mono:spring-doc.cadn.net.cn

Java
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) (1)
            .andExpect(request().asyncStarted()) (2)
            .andExpect(request().asyncResult("body")) (3)
            .andReturn();

    this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect(status().isOk()) (5)
            .andExpect(content().string("body"));
}
1 检查响应状态是否保持不变
2 异步处理必须已启动
3 等待并断言异步结果
4 手动执行 ASYNC 调度(因为没有正在运行的容器)
5 验证最终响应
Kotlin
@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status { isOk() } (1)
        request { asyncStarted() } (2)
        // TODO Remove unused generic parameter
        request { asyncResult<Nothing>("body") } (3)
    }.andReturn()


    mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect {
                status { isOk() } (5)
                content().string("body")
            }
}
1 检查响应状态是否保持不变
2 异步处理必须已启动
3 等待并断言异步结果
4 手动执行 ASYNC 调度(因为没有正在运行的容器)
5 验证最终响应
流式响应

您可以使用WebTestClient以测试流式处理响应,例如服务器发送的事件。然而MockMvcWebTestClient不支持无限 流,因为无法从客户端取消服务器流。 要测试无限流,您需要绑定到正在运行的服务器, 或者在使用 Spring Boot 时,使用正在运行的服务器进行测试spring-doc.cadn.net.cn

筛选注册

设置MockMvc实例,您可以注册一个或多个 ServletFilter实例,如以下示例所示:spring-doc.cadn.net.cn

Java
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

已注册的过滤器通过MockFilterChainspring-test和 最后一个过滤器委托给DispatcherServlet.spring-doc.cadn.net.cn

MockMvc 与端到端测试

MockMVc 是建立在 Servlet API 模拟实现之上的spring-test模块,并且不依赖于正在运行的容器。因此,有 与实际 客户端和正在运行的实时服务器。spring-doc.cadn.net.cn

考虑这个问题的最简单方法是从空白开始MockHttpServletRequest. 无论你添加什么,请求都会变成什么。可能会让你措手不及的事情 是默认情况下没有上下文路径;不jsessionid饼干;无转发, 错误或异步调度;因此,没有实际的 JSP 渲染。相反 “转发”和“重定向”URL 保存在MockHttpServletResponse并且可以 被期望断言。spring-doc.cadn.net.cn

这意味着,如果您使用 JSP,则可以验证请求所在的 JSP 页面 转发,但不呈现 HTML。换句话说,不会调用 JSP。注意 但是,所有其他不依赖于转发的渲染技术,例如 Thymeleaf 和 Freemarker,按预期将 HTML 呈现到响应正文。同样的道理 用于通过@ResponseBody方法。spring-doc.cadn.net.cn

或者,您可以考虑从 Spring Boot 与@SpringBootTest.请参阅 Spring Boot 参考指南spring-doc.cadn.net.cn

每种方法各有利弊。Spring MVC Test 中提供的选项包括 从经典单元测试到完全集成测试的不同站点。成为 当然,春季 MVC 测试中的任何选项都不属于经典单元类别 测试,但他们离它更近了一点。例如,您可以隔离 Web 图层 通过将模拟服务注入控制器,在这种情况下,您正在测试 Web 层仅通过DispatcherServlet但使用实际的 Spring 配置,就像你一样 可能会将数据访问层与其上方的层隔离开来测试。此外,您还可以使用 独立设置,一次专注于一个控制器并手动提供 使其正常工作所需的配置。spring-doc.cadn.net.cn

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

更多示例

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

3.7.2. HtmlUnit 集成

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

MockMvc 使用不依赖于 Servlet 容器的模板技术(例如,Thymeleaf、FreeMarker 等),但它不适用于 JSP,因为它们依赖于 Servlet 容器。
为什么选择 HtmlUnit 集成?

我想到的最明显的问题是“我为什么需要这个?”答案是最好通过探索一个非常基本的示例应用程序来找到。假设你有一个 Spring MVC Web应用程序,它支持 CRUD作Message对象。 该应用程序还支持分页所有消息。您将如何测试它?spring-doc.cadn.net.cn

使用 Spring MVC Test,我们可以轻松测试我们是否能够创建一个Message如下:spring-doc.cadn.net.cn

Java
MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
@Test
fun test() {
    mockMvc.post("/messages/") {
        param("summary", "Spring Rocks")
        param("text", "In case you didn't know, Spring Rocks!")
    }.andExpect {
        status().is3xxRedirection()
        redirectedUrl("/messages/123")
    }
}

如果我们想测试允许我们创建消息的表单视图怎么办? 例如 假设我们的表单如下所示:spring-doc.cadn.net.cn

<form id="messageForm" action="/messages/" method="post">
    <div class="pull-right"><a href="/messages/">Messages</a></div>

    <label for="summary">Summary</label>
    <input type="text" class="required" id="summary" name="summary" value="" />

    <label for="text">Message</label>
    <textarea id="text" name="text"></textarea>

    <div class="form-actions">
        <input type="submit" value="Create" />
    </div>
</form>

我们如何确保我们的表单生成正确的请求来创建新消息? 一个 天真的尝试可能类似于以下内容:spring-doc.cadn.net.cn

Java
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
Kotlin
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}

此测试有一些明显的缺点。如果我们更新控制器以使用message而不是text,我们的表单测试继续通过,即使 HTML 表单与控制器不同步。为了解决这个问题,我们可以将我们的两个测试结合起来,作为 遵循:spring-doc.cadn.net.cn

Java
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
        .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());

MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param(summaryParamName, "Spring Rocks")
        .param(textParamName, "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='$summaryParamName']") { exists() }
    xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
    param(summaryParamName, "Spring Rocks")
    param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
    status().is3xxRedirection()
    redirectedUrl("/messages/123")
}

这将降低我们的测试错误通过的风险,但仍然有一些 问题:spring-doc.cadn.net.cn

  • 如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂:是 字段类型正确?字段是否已启用?等等。spring-doc.cadn.net.cn

  • 另一个问题是,我们所做的工作是我们预期的两倍。我们必须首先 验证视图,然后我们使用刚刚验证的相同参数提交视图。 理想情况下,这可以一次完成。spring-doc.cadn.net.cn

  • 最后,我们仍然无法解释一些事情。例如,如果表单有JavaScript 验证,我们也希望测试呢?spring-doc.cadn.net.cn

总体问题是测试网页不涉及单一交互。相反,它是用户如何与网页交互以及该网页如何交互的组合页面与其他资源交互。例如,表单视图的结果用作用户创建消息的输入。此外,我们的表单视图可能会使用影响页面行为的其他资源,例如 JavaScript 验证。spring-doc.cadn.net.cn

集成测试来拯救?

为了解决前面提到的问题,我们可以执行端到端集成测试,但这有一些缺点。考虑测试允许我们翻阅 消息。 我们可能需要以下测试:spring-doc.cadn.net.cn

要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致许多额外的挑战:spring-doc.cadn.net.cn

这些挑战并不意味着我们应该放弃端到端的集成测试 完全。相反,我们可以减少端到端集成测试的数量 重构我们的详细测试以使用运行更快、更可靠的模拟服务, 并且没有副作用。然后我们可以实现少量真正的端到端 验证简单工作流程的集成测试,以确保一切协同工作 适当地。spring-doc.cadn.net.cn

进入 HtmlUnit 集成

那么我们如何在测试页面的交互和仍然之间取得平衡 在我们的测试套件中保持良好的性能?答案是:“通过集成 MockMvc 与 HtmlUnit 一起使用。spring-doc.cadn.net.cn

HtmlUnit 集成选项

当您想要将 MockMvc 与 HtmlUnit 集成时,您有多种选择:spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit

本节介绍如何集成 MockMvc 和 HtmlUnit。如果需要,请使用此选项使用原始 HtmlUnit 库。spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit 设置

首先,确保已包含对net.sourceforge.htmlunit:htmlunit.为了将 HtmlUnit 与 Apache HttpComponents 一起使用 4.5+,需要使用HtmlUnit 2.18或更高版本。spring-doc.cadn.net.cn

我们可以轻松创建一个 HtmlUnitWebClient通过使用MockMvcWebClientBuilder如下:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
这是使用MockMvcWebClientBuilder.对于高级使用, 看高深MockMvcWebClientBuilder.

这可确保引用的任何 URLlocalhost因为服务器被定向到我们的MockMvc实例,无需真正的 HTTP 连接。任何其他 URL 都是 像往常一样使用网络连接请求。这使我们可以轻松测试 CDN。spring-doc.cadn.net.cn

MockMvc 和 HtmlUnit 用法

现在我们可以像往常一样使用 HtmlUnit,但无需部署 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息,其中包含以下内容:spring-doc.cadn.net.cn

Java
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Kotlin
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
默认上下文路径为 。或者,我们可以指定上下文路径, 如""高深MockMvcWebClientBuilder.

一旦我们引用了HtmlPage,然后我们可以填写表格并提交 创建消息,如以下示例所示:spring-doc.cadn.net.cn

Java
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
Kotlin
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()

最后,我们可以验证新消息是否已成功创建。以下内容 断言使用 AssertJ 库:spring-doc.cadn.net.cn

Java
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
Kotlin
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")

前面的代码在许多方面改进了我们的 MockMvc 测试。 首先,我们不再需要显式验证我们的表单,然后创建一个请求 看起来像表格。相反,我们索取表格、填写并提交,从而 显着降低开销。spring-doc.cadn.net.cn

另一个重要因素是 HtmlUnit 使用 Mozilla Rhino 引擎来评估 JavaScript。这意味着我们也可以测试 JavaScript 在我们页面中的行为。spring-doc.cadn.net.cn

请参阅 HtmlUnit 文档 有关使用 HtmlUnit 的其他信息。spring-doc.cadn.net.cn

高深MockMvcWebClientBuilder

在到目前为止的示例中,我们使用了MockMvcWebClientBuilder以最简单的方式 可能,通过构建一个WebClient基于WebApplicationContext为我们加载 Spring TestContext 框架。在以下示例中重复此方法:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}

我们还可以指定其他配置选项,如以下示例所示:spring-doc.cadn.net.cn

Java
WebClient webClient;

@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build()
}

作为替代方案,我们可以通过配置MockMvc实例,并将其提供给MockMvcWebClientBuilder如下:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

这更冗长,但是,通过构建WebClient使用MockMvc实例,我们有 MockMvc 的全部功能触手可及。spring-doc.cadn.net.cn

有关创建MockMvc实例,请参阅设置选项
MockMvc 和 WebDriver

在前面的部分中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API 的 API。在本节中,我们在 Selenium WebDriver 中使用其他抽象来使事情变得更加容易。spring-doc.cadn.net.cn

为什么选择 WebDriver 和 MockMvc?

我们已经可以使用 HtmlUnit 和 MockMvc,那么我们为什么要使用 WebDriver?这 Selenium WebDriver 提供了一个非常优雅的 API,让我们可以轻松组织代码。自 更好地展示它是如何工作的,我们将在本节中探讨一个示例。spring-doc.cadn.net.cn

尽管是 Selenium 的一部分,但 WebDriver 没有 需要 Selenium 服务器来运行您的测试。

假设我们需要确保正确创建消息。测试涉及发现 HTML 表单输入元素,填写它们,并做出各种断言。spring-doc.cadn.net.cn

这种方法会导致许多单独的测试,因为我们想要测试错误条件 也。例如,我们希望确保如果我们只填写部分 表格。如果我们填写整个表格,则应显示新创建的消息 之后。spring-doc.cadn.net.cn

如果其中一个字段被命名为“summary”,我们可能会有类似于 以下在我们的测试中的多个地方重复:spring-doc.cadn.net.cn

Java
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
Kotlin
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)

那么,如果我们将idsmmry?这样做将迫使我们更新所有 纳入这一变化的测试。这违反了 DRY 原则,所以我们应该 理想情况下,将此代码提取到自己的方法中,如下所示:spring-doc.cadn.net.cn

Java
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}

public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}
Kotlin
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
    setSummary(currentPage, summary);
    // ...
}

fun setSummary(currentPage:HtmlPage , summary: String) {
    val summaryInput = currentPage.getHtmlElementById("summary")
    summaryInput.setValueAttribute(summary)
}

这样做可以确保我们在更改 UI 时不必更新所有测试。spring-doc.cadn.net.cn

我们甚至可以更进一步,将此逻辑放在Object那 表示HtmlPage我们当前处于 ON,如以下示例所示:spring-doc.cadn.net.cn

Java
public class CreateMessagePage {

    final HtmlPage currentPage;

    final HtmlTextInput summaryInput;

    final HtmlSubmitInput submit;

    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }

    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);

        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);

        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }

    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }

    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}
Kotlin
    class CreateMessagePage(private val currentPage: HtmlPage) {

        val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")

        val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")

        fun <T> createMessage(summary: String, text: String): T {
            setSummary(summary)

            val result = submit.click()
            val error = at(result)

            return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
        }

        fun setSummary(summary: String) {
            summaryInput.setValueAttribute(summary)
        }

        fun at(page: HtmlPage): Boolean {
            return "Create Message" == page.getTitleText()
        }
    }
}

以前,此模式称为页面对象模式。虽然我们 当然可以使用 HtmlUnit 来做到这一点,WebDriver 提供了一些我们在 以下部分将使此模式更易于实现。spring-doc.cadn.net.cn

MockMvc 和 WebDriver 设置

要将 Selenium WebDriver 与 Spring MVC Test 框架一起使用,请确保您的项目 包括对org.seleniumhq.selenium:selenium-htmlunit-driver.spring-doc.cadn.net.cn

我们可以轻松创建一个与 MockMvc 集成的 Selenium WebDriver,方法是使用MockMvcHtmlUnitDriverBuilder如以下示例所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
这是使用MockMvcHtmlUnitDriverBuilder.对于更高级的 用法,请参阅高深MockMvcHtmlUnitDriverBuilder.

前面的示例可确保引用的任何 URLlocalhost因为服务器是 直接发送给我们的MockMvc实例,无需真正的 HTTP 连接。任何其他 像往常一样,使用网络连接请求 URL。这使我们可以轻松地测试 CDN 的使用。spring-doc.cadn.net.cn

MockMvc 和 WebDriver 的使用

现在我们可以像往常一样使用 WebDriver,但无需部署我们的 应用程序添加到 Servlet 容器中。例如,我们可以请求视图创建一个 消息,其中包含以下内容:spring-doc.cadn.net.cn

Java
CreateMessagePage page = CreateMessagePage.to(driver);
Kotlin
val page = CreateMessagePage.to(driver)

然后,我们可以填写表格并提交以创建消息,如下所示:spring-doc.cadn.net.cn

Java
ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
Kotlin
val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)

这通过利用页面对象模式改进了 HtmlUnit 测试的设计。正如我们在为什么选择 WebDriver 和 MockMvc?中提到的,我们可以使用页面对象模式 使用 HtmlUnit,但使用 WebDriver 要容易得多。考虑以下几点CreateMessagePage实现:spring-doc.cadn.net.cn

Java
public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;
    private WebElement text;

    (3)
    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
1 CreateMessagePage扩展AbstractPage.我们不讨论细节AbstractPage,但总而言之,它包含我们所有页面的通用功能。 例如,如果我们的应用程序有一个导航栏、全局错误消息和其他 功能,我们可以将此逻辑放在共享位置。
2 我们所在的 HTML 页面的每个部分都有一个成员变量 感兴趣。这些类型WebElement.WebDriver 的PageFactory让我们删除一个 HtmlUnit 版本中的大量代码CreateMessagePage通过自动解析 每WebElement. 这PageFactory#initElements(WebDriver,Class<T>)方法会自动解析每个WebElement通过使用字段名称并查找它 通过idnameHTML 页面中的元素。
3 我们可以使用@FindBy注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy注释来查找我们的提交按钮,并带有css选择器 (input[type=submit])。
Kotlin
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)

    (2)
    private lateinit var summary: WebElement
    private lateinit var text: WebElement

    (3)
    @FindBy(css = "input[type=submit]")
    private lateinit var submit: WebElement

    fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
        this.summary.sendKeys(summary)
        text.sendKeys(details)
        submit.click()
        return PageFactory.initElements(driver, resultPage)
    }
    companion object {
        fun to(driver: WebDriver): CreateMessagePage {
            driver.get("http://localhost:9990/mail/messages/form")
            return PageFactory.initElements(driver, CreateMessagePage::class.java)
        }
    }
}
1 CreateMessagePage扩展AbstractPage.我们不讨论细节AbstractPage,但总而言之,它包含我们所有页面的通用功能。 例如,如果我们的应用程序有一个导航栏、全局错误消息和其他 功能,我们可以将此逻辑放在共享位置。
2 我们所在的 HTML 页面的每个部分都有一个成员变量 感兴趣。这些类型WebElement.WebDriver 的PageFactory让我们删除一个 HtmlUnit 版本中的大量代码CreateMessagePage通过自动解析 每WebElement. 这PageFactory#initElements(WebDriver,Class<T>)方法会自动解析每个WebElement通过使用字段名称并查找它 通过idnameHTML 页面中的元素。
3 我们可以使用@FindBy注解以覆盖默认查找行为。我们的示例展示了如何使用@FindBy注释来查找我们的提交按钮,并带有css选择器 (input[type=submit])。

最后,我们可以验证新消息是否已成功创建。以下断言使用 AssertJ 断言库:spring-doc.cadn.net.cn

Java
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
Kotlin
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")

我们可以看到,我们的ViewMessagePage让我们与自定义域模型进行交互。 为 示例,它公开了一个返回Message对象:spring-doc.cadn.net.cn

Java
public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
Kotlin
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())

然后,我们可以在断言中使用富域对象。spring-doc.cadn.net.cn

最后,我们不能忘记关闭WebDriver实例,当测试完成时, 如下:spring-doc.cadn.net.cn

Java
@AfterEach
void destroy() {
    if (driver != null) {
        driver.close();
    }
}
Kotlin
@AfterEach
fun destroy() {
    if (driver != null) {
        driver.close()
    }
}

有关使用 WebDriver 的更多信息,请参阅 Selenium WebDriver 文档spring-doc.cadn.net.cn

高深MockMvcHtmlUnitDriverBuilder

在到目前为止的示例中,我们使用了MockMvcHtmlUnitDriverBuilder以最简单的方式 可能,通过构建一个WebDriver基于WebApplicationContext为我们加载 Spring TestContext 框架。此处重复此方法,如下所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}

我们还可以指定其他配置选项,如下所示:spring-doc.cadn.net.cn

Java
WebDriver driver;

@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build()
}

作为替代方案,我们可以通过配置MockMvc实例,并将其提供给MockMvcHtmlUnitDriverBuilder如下:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

这更冗长,但是,通过构建WebDriver使用MockMvc实例,我们有 MockMvc 的全部功能触手可及。spring-doc.cadn.net.cn

有关创建MockMvc实例,请参阅设置选项
MockMvc 和 Geb

在上一节中,我们看到了如何将 MockMvc 与 WebDriver 一起使用。在本节中,我们将 使用 Geb 使我们的测试更加 Groovy-er。spring-doc.cadn.net.cn

为什么选择 Geb 和 MockMvc?

Geb 由 WebDriver 提供支持,因此它提供了许多与我们相同的好处 Web驱动程序。然而,Geb 通过处理一些 样板代码。spring-doc.cadn.net.cn

MockMvc 和 Geb 设置

我们可以轻松初始化一个 GebBrowser使用 MockMvc 的 Selenium WebDriver,作为 遵循:spring-doc.cadn.net.cn

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
这是使用MockMvcHtmlUnitDriverBuilder.对于更高级的 用法,请参阅高深MockMvcHtmlUnitDriverBuilder.

这确保了任何 URL 引用localhost因为服务器被定向到我们的MockMvc实例,无需真正的 HTTP 连接。任何其他 URL 都是 正常使用网络连接请求。这使我们可以轻松测试 CDN。spring-doc.cadn.net.cn

MockMvc 和 Geb 用法

现在我们可以像往常一样使用 Geb,但无需将我们的应用程序部署到 一个 Servlet 容器。例如,我们可以请求视图使用 以后:spring-doc.cadn.net.cn

to CreateMessagePage

然后,我们可以填写表格并提交以创建消息,如下所示:spring-doc.cadn.net.cn

when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)

任何未找到的无法识别的方法调用或属性访问或引用都是 转发到当前页面对象。这删除了我们 直接使用 WebDriver 时需要。spring-doc.cadn.net.cn

与直接使用 WebDriver 一样,这通过使用 Page 对象改进了 HtmlUnit 测试的设计 模式。如前所述,我们可以将页面对象模式与 HtmlUnit 一起使用,并且 WebDriver,但使用 Geb 会更容易。考虑我们新的基于 GroovyCreateMessagePage实现:spring-doc.cadn.net.cn

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

我们CreateMessagePage延伸Page.我们不讨论细节Page,但是,在summary 中,它包含我们所有页面的通用功能。我们定义了一个 URL,其中可以找到这个页面。这让我们可以导航到该页面,如下所示:spring-doc.cadn.net.cn

to CreateMessagePage

我们还有一个at关闭,确定我们是否在指定的页面。它应该 返回true如果我们在正确的页面上。这就是为什么我们可以断言我们位于正确的页面,如下所示:spring-doc.cadn.net.cn

then:
at CreateMessagePage
errors.contains('This field is required.')
我们在闭包中使用断言,以便我们可以确定哪里出了问题如果我们到了错误的页面。

接下来,我们创建一个content关闭,指定 页。我们可以使用 jQuery 式的导航器 API 来选择我们感兴趣的内容。spring-doc.cadn.net.cn

最后,我们可以验证新消息是否已成功创建,如下所示:spring-doc.cadn.net.cn

then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

有关如何充分利用 Geb 的更多详细信息,请参阅 The Book of Geb 用户手册。spring-doc.cadn.net.cn

3.8. 测试客户端应用程序

您可以使用客户端测试来测试内部使用RestTemplate.这 想法是声明预期的请求并提供“存根”响应,以便您可以 专注于单独测试代码(即不运行服务器)。以下内容 示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());

// Test code that uses the above RestTemplate ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())

// Test code that uses the above RestTemplate ...

mockServer.verify()

在前面的示例中,MockRestServiceServer(客户端 REST 的中心类 tests) 配置RestTemplate使用自定义ClientHttpRequestFactory那 根据预期断言实际请求并返回“存根”响应。在这个 case 时,我们期望请求/greeting并希望返回 200 响应text/plain内容。我们可以将其他预期请求和存根响应定义为 需要。当我们定义预期请求和存根响应时,RestTemplate可以 像往常一样在客户端代码中使用。测试结束时,mockServer.verify()可以 用于验证所有期望是否已得到满足。spring-doc.cadn.net.cn

默认情况下,请求按声明预期的顺序进行。你 可以设置ignoreExpectOrder选项,在这种情况下,所有 检查期望值(按顺序)以查找给定请求的匹配项。这意味着 允许以任何顺序提出请求。以下示例使用ignoreExpectOrder:spring-doc.cadn.net.cn

Java
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
Kotlin
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()

即使默认情况下使用无序请求,每个请求也只允许运行一次。 这expect方法提供了一个重载的变体,该变体接受ExpectedCount指定计数范围的参数(例如,once,manyTimes,max,min,between,依此类推)。以下示例使用times:spring-doc.cadn.net.cn

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());

// ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())

// ...

mockServer.verify()

请注意,当ignoreExpectOrder未设置(默认值),因此请求 是预期的,则该顺序仅适用于任何 预期请求。例如,如果“/something”需要两次,然后是 “/somewhere”三次,那么在出现之前应该有对“/something”的请求 对“/somewhere”的请求,但是,除了随后的“/something”和“/somewhere”之外, 请求可以随时提出。spring-doc.cadn.net.cn

作为上述所有方法的替代方法,客户端测试支持还提供了ClientHttpRequestFactory您可以配置到RestTemplate自 将其绑定到MockMvc实例。这允许使用实际的服务器端处理请求 逻辑,但不运行服务器。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...
Kotlin
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))

// Test code that uses the above RestTemplate ...

3.8.1. 静态导入

与服务器端测试一样,用于客户端测试的 Fluent API 需要一些静态 进口。 通过搜索很容易找到这些MockRest*. Eclipse 用户应该添加MockRestRequestMatchers.*MockRestResponseCreators.*如 “最喜欢的静态成员” 在 Java → 编辑器下的 Eclipse 首选项中→内容辅助→收藏夹。这允许在键入 的第一个字符后使用内容辅助静态方法名称。其他 IDE(例如 IntelliJ)可能不需要任何其他 配置。 检查静态成员是否支持代码完成。spring-doc.cadn.net.cn

3.8.2. 客户端 REST 测试的更多示例

Spring MVC Test 自己的测试包括示例 客户端 REST 测试的测试。spring-doc.cadn.net.cn

4. 更多资源

有关测试的更多信息,请参阅以下资源:spring-doc.cadn.net.cn