Testing

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

1.Spring 测试简介

测试是企业软件开发的组成部分。本章重点介绍 IoC 原则为unit testing带来的增值,以及 Spring 框架对integration testing的支持所带来的好处。 (对企业中测试的彻底处理不在本参考手册的范围之内.)

2.单元测试

与传统的 Java EE 开发相比,依赖注入应该使您的代码对容器的依赖程度降低。组成应用程序的 POJO 应该可以在 JUnit 或 TestNG 测试中进行测试,并使用new运算符实例化对象,而无需使用 Spring 或任何其他容器。您可以使用mock objects(结合其他有价值的测试技术)来单独测试代码。如果您遵循 Spring 的体系结构建议,那么代码库的最终分层和组件化将使单元测试更加容易。例如,您可以通过存根或模拟 DAO 或存储库接口来测试服务层对象,而无需在运行单元测试时访问持久性数据。

true 的单元测试通常运行非常快,因为没有可设置的运行时基础架构。将 true 的单元测试作为开发方法的一部分可以提高生产率。您可能不需要测试章节的这一部分来帮助您为基于 IoC 的应用程序编写有效的单元测试。但是,对于某些单元测试方案,Spring 框架提供了模拟对象和测试支持类,本章对此进行了介绍。

2.1. 模拟对象

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

2.1.1. Environment

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

2.1.2. JNDI

org.springframework.mock.jndi软件包包含 JNDI SPI 的实现,可用于为测试套件或独立应用程序设置简单的 JNDI 环境。例如,如果 JDBC DataSource实例在测试代码中与在 Java EE 容器中绑定到相同的 JNDI 名称,则可以在测试场景中重用应用程序代码和配置,而无需进行修改。

2.1.3. Servlet API

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

Tip

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

Spring MVC 测试框架构建在模拟 Servlet API 对象的基础上,为 Spring MVC 提供了集成测试框架。参见Spring MVC 测试框架

2.1.4. Spring WebReactive

org.springframework.mock.http.server.reactive程序包包含用于 WebFlux 应用程序的ServerHttpRequestServerHttpResponse的模拟实现。 org.springframework.mock.web.server软件包包含一个模拟ServerWebExchange,该模拟ServerWebExchange取决于那些模拟请求和响应对象。

MockServerHttpRequestMockServerHttpResponse都从与特定于服务器的实现相同的抽象 Base Class 扩展,并与它们共享行为。例如,模拟请求一旦创建便是不可变的,但是您可以使用ServerHttpRequest中的mutate()方法来创建修改后的实例。

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

WebTestClient构建在模拟请求和响应的基础上,以提供对不使用 HTTP 服务器的 WebFlux 应用程序测试的支持。Client 端还可以用于正在运行的服务器的端到端测试。

2.2. 单元测试支持类

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

2.2.1. 通用测试工具

org.springframework.test.util软件包包含几个通用 Util,可用于单元和集成测试。

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

AopTestUtils是与 AOP 相关的 Util 方法的集合。您可以使用这些方法来获取对隐藏在一个或多个 Spring 代理后面的基础目标对象的引用。例如,如果您已通过使用 EasyMock 或 Mockito 之类的库将 bean 配置为动态模拟,并且该模拟包装在 Spring 代理中,则可能需要直接访问基础模拟以配置对它的期望并执行验证。有关 Spring 的核心 AOPUtil,请参见AopUtilsAopProxyUtils

2.2.2. Spring MVC 测试 Util

org.springframework.test.web软件包包含ModelAndViewAssert,您可以将它们与 JUnit,TestNG 或任何其他测试框架结合使用,以进行处理 Spring MVC ModelAndView对象的单元测试。

Unit testing Spring MVC Controllers

要对作为 POJO 的 Spring MVC Controller类进行单元测试,请使用ModelAndViewAssert结合MockHttpServletRequestMockHttpSession等,以此类推。为了对 Spring MVC 和 REST Controller类以及 Spring MVC 的WebApplicationContext配置进行全面的集成测试,请改用Spring MVC 测试框架

3.集成测试

本节(本章其余部分)涵盖了 Spring 应用程序的集成测试。它包括以下主题:

3.1. Overview

能够执行一些集成测试而无需部署到应用程序服务器或连接到其他企业基础结构,这一点很重要。这样做可以测试以下内容:

Spring 框架为spring-test模块中的集成测试提供了一流的支持。实际的 JAR 文件的名称可能包括发行版,也可能采用长org.springframework.test格式,具体取决于从何处获取(说明请参见依赖 Management 部分)。该库包含org.springframework.test软件包,该软件包包含用于与 Spring 容器进行集成测试的有价值的类。此测试不依赖于应用程序服务器或其他部署环境。此类测试的运行速度比单元测试慢,但比依赖于部署到应用程序服务器的等效 Selenium 测试或远程测试快得多。

在 Spring 2.5 和更高版本中,以 Comments 驱动的Spring TestContext 框架形式提供了单元和集成测试支持。 TestContext 框架与实际使用的测试框架无关,该框架允许在各种环境(包括 JUnit,TestNG 和其他环境)中对测试进行检测。

3.2. 集成测试的目标

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

接下来的几节描述了每个目标,并提供了有关实现和配置详细信息的链接。

3.2.1. 上下文 Management 和缓存

Spring TestContext Framework 提供了 Spring ApplicationContext实例和WebApplicationContext实例的一致加载以及这些上下文的缓存。支持加载上下文的缓存很重要,因为启动时间可能会成为一个问题-不是因为 Spring 本身的开销,而是因为 Spring 容器实例化的对象需要时间才能实例化。例如,具有 50 到 100 个 HibernateMap 文件的项目可能需要 10 到 20 秒钟来加载 Map 文件,并且在每个测试夹具中运行每个测试之前要承担该费用,这会导致整体测试运行速度变慢,从而降低开发人员的工作效率。

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

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

参见Context ManagementContext Caching与 TestContext 框架。

3.2.2. 测试夹具的依赖注入

当 TestContext 框架加载您的应用程序上下文时,可以选择使用依赖注入来配置测试类的实例。这提供了一种方便的机制,可以通过在应用程序上下文中使用预配置的 bean 来设置测试装置。此处的一个强大好处是您可以在各种测试场景中重用应用程序上下文(例如,用于配置 SpringManagement 的对象图,事务代理,DataSource实例等),从而避免了为单个测试复制复杂的测试夹具设置的需要案件。

例如,考虑一个场景,其中我们有一个类(HibernateTitleRepository),该类实现Title域实体的数据访问逻辑。我们要编写集成测试来测试以下方面:

请参阅TestContext framework依赖测试夹具的依赖项注入。

3.2.3. TransactionManagement

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

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

如果您要提交事务(这不常见,但在希望特定测试填充或修改数据库时偶尔有用),则可以通过使用@CommitComments 告诉 TestContext 框架来提交事务,而不是回滚。

请参阅使用TestContext framework进行 TransactionManagement。

3.2.4. 集成测试支持类

Spring TestContext Framework 提供了几个abstract支持类,这些类简化了集成测试的编写。这些基础测试类为测试框架提供了定义明确的钩子,还提供了方便的实例变量和方法,使您可以访问:

此外,您可能希望使用针对项目的实例变量和方法来创建自己的自定义,应用程序级超类。

请参阅TestContext framework的支持类。

3.3. JDBC 测试支持

org.springframework.test.jdbc软件包包含JdbcTestUtils,这是 JDBC 相关 Util 功能的集合,旨在简化标准数据库测试方案。具体地说,JdbcTestUtils提供了以下静态 Util 方法。

Tip

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

spring-jdbc模块提供了对配置和启动嵌入式数据库的支持,您可以在与数据库交互的集成测试中使用它。有关详细信息,请参见嵌入式数据库支持使用嵌入式数据库测试数据访问逻辑

3.4. Annotations

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

3.4.1. Spring 测试 Comments

Spring 框架提供了以下特定于 Spring 的 Comments 集,您可以在单元测试和集成测试中将它们与 TestContext 框架结合使用。有关更多信息,请参见相应的 javadoc,包括默认属性值,属性别名和其他详细信息。

Spring 的测试 Comments 包括以下内容:

@BootstrapWith

@BootstrapWith是类级别的 Comments,可用于配置如何引导 Spring TestContext Framework。具体来说,您可以使用@BootstrapWith指定自定义TestContextBootstrapper。有关更多详细信息,请参见引导 TestContext 框架部分。

@ContextConfiguration

@ContextConfiguration定义了类级别的元数据,用于确定如何加载和配置ApplicationContext以进行集成测试。具体地说,@ContextConfiguration声明了用于加载上下文的应用程序上下文资源locations或带 Comments 的classes

资源位置通常是位于 Classpath 中的 XML 配置文件或 Groovy 脚本,而带 Comments 的类通常是@Configuration类。但是,资源位置也可以引用文件系统中的文件和脚本,带 Comments 的类可以是组件类,依此类推。

以下示例显示了一个@ContextConfigurationComments,该 Comments 引用了 XML 文件:

@ContextConfiguration("/test-config.xml") (1)
public class XmlApplicationContextTests {
    // class body...
}

以下示例显示了一个@ContextConfigurationComments,该 Comments 引用一个类:

@ContextConfiguration(classes = TestConfig.class) (1)
public class ConfigClassApplicationContextTests {
    // class body...
}

作为声明资源位置或带 Comments 的类的替代方法或补充,您可以使用@ContextConfiguration声明ApplicationContextInitializer类。以下示例显示了这种情况:

@ContextConfiguration(initializers = CustomContextIntializer.class) (1)
public class ContextInitializerTests {
    // class body...
}

您也可以选择使用@ContextConfiguration来声明ContextLoader策略。但是请注意,由于默认加载器支持initializers以及资源locations或带 Comments 的classes,因此通常不需要显式配置加载器。

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

@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
public class CustomLoaderXmlApplicationContextTests {
    // class body...
}

Note

@ContextConfiguration为继承资源位置或配置类以及超类声明的上下文初始化程序提供支持。

有关更多详细信息,请参见Context Management@ContextConfiguration javadocs。

@WebAppConfiguration

@WebAppConfiguration是类级别的 Comments,您可以用来声明为集成测试加载的ApplicationContext应该是WebApplicationContext。在测试类上仅存在@WebAppConfiguration可以确保为测试加载WebApplicationContext,并使用默认值"file:src/main/webapp"作为 Web 应用程序根目录的路径(即资源基础路径)。资源基础路径在幕后用于创建MockServletContext,它用作测试WebApplicationContextServletContext

以下示例显示了如何使用@WebAppConfiguration注解:

@ContextConfiguration
@WebAppConfiguration (1)
public class WebAppTests {
    // class body...
}

要覆盖默认值,可以使用隐式value属性指定其他基础资源路径。 classpath:file:资源前缀均受支持。如果未提供资源前缀,则假定该路径是文件系统资源。以下示例显示如何指定 Classpath 资源:

@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
public class WebAppTests {
    // class body...
}

请注意,必须在单个测试类中或在测试类层次结构中将@WebAppConfiguration@ContextConfiguration结合使用。有关更多详细信息,请参见@WebAppConfiguration javadoc。

@ContextHierarchy

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

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

如果需要合并或覆盖测试类层次结构中给定级别的上下文层次结构的配置,则必须通过在类层次结构中每个相应级别上为@ContextConfigurationname属性提供相同的值来显式命名该级别。有关更多示例,请参见Context Hierarchies@ContextHierarchy javadoc。

@ActiveProfiles

@ActiveProfiles是类级别的 Comments,用于声明在加载ApplicationContext进行集成测试时应激活哪些 bean 定义配置文件。

以下示例表明devProfile 应处于活动状态:

@ContextConfiguration
@ActiveProfiles("dev") (1)
public class DeveloperTests {
    // class body...
}

以下示例表明devintegration配置文件均应处于活动状态:

@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
public class DeveloperIntegrationTests {
    // class body...
}

Note

@ActiveProfiles默认情况下支持继承超类声明的活动 bean 定义配置文件。您还可以通过实现自定义ActiveProfilesResolver并使用@ActiveProfilesresolver属性对其进行编程,从而以编程方式解析活动 bean 定义概要文件。

有关示例和更多详细信息,请参见使用环境配置文件进行上下文配置@ActiveProfiles javadoc。

@TestPropertySource

@TestPropertySource是类级别的注解,可用于配置属性文件和内联属性的位置,以将其添加到Environment中的PropertySources集合中,以便为集成测试加载ApplicationContext

测试属性源的优先级高于从 os 环境或 Java 系统属性以及应用程序通过@PropertySource或以编程方式添加的属性源加载的属性。因此,测试属性源可用于选择性覆盖系统和应用程序属性源中定义的属性。此外,内联属性比从资源位置加载的属性具有更高的优先级。

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

@ContextConfiguration
@TestPropertySource("/test.properties") (1)
public class MyIntegrationTests {
    // class body...
}

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

@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
public class MyIntegrationTests {
    // class body...
}
@DirtiesContext

@DirtiesContext表示基础 Spring ApplicationContext在执行测试期间已被弄脏(即,测试以某种方式修改或破坏了它,例如,通过更改单例 bean 的状态),因此应将其关闭。当应用程序上下文标记为脏时,会将其从测试框架的缓存中删除并关闭。因此,将为需要上下文具有相同配置元数据的任何后续测试重建基础 Spring 容器。

您可以将@DirtiesContext用作同一类或类层次结构中的类级别和方法级别的 Comments。在这种情况下,取决于配置的methodModeclassMode,在任何此类带 Comments 的方法之前或之后以及当前测试类之前或之后,ApplicationContext被标记为脏。

以下示例说明了在各种配置情况下何时弄脏上下文:

@DirtiesContext(classMode = BEFORE_CLASS) (1)
public class FreshContextTests {
    // some tests that require a new Spring container
}
@DirtiesContext (1)
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
public class FreshContextTests {
    // some tests that require a new Spring container
}
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
@DirtiesContext(methodMode = BEFORE_METHOD) (1)
@Test
public void testProcessWhichRequiresFreshAppCtx() {
    // some logic that requires a new Spring container
}
@DirtiesContext (1)
@Test
public void testProcessWhichDirtiesAppCtx() {
    // some logic that results in the Spring container being dirtied
}

如果在测试中使用@DirtiesContext且上下文使用@ContextHierarchy配置为上下文层次结构的一部分,则可以使用hierarchyMode标志来控制如何清除上下文缓存。默认情况下,使用穷举算法清除上下文缓存,不仅包括当前级别,还包括共享当前测试共有的祖先上下文的所有其他上下文层次结构。驻留在公共祖先上下文的子层次结构中的所有ApplicationContext实例将从上下文缓存中删除并关闭。如果穷举算法对于特定用例而言过于矫 kill 过正,则可以指定更简单的当前级别算法,如以下示例所示。

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

public class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    public void test() {
        // some logic that results in the child context being dirtied
    }
}

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

@TestExecutionListeners

@TestExecutionListeners定义了用于配置TestExecutionListener实现的类级元数据,该实现应向TestContextManager注册。通常,@TestExecutionListeners@ContextConfiguration结合使用。

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

@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
public class CustomTestExecutionListenerTests {
    // class body...
}

默认情况下,@TestExecutionListeners支持继承的侦听器。有关示例和更多详细信息,请参见javadoc

@Commit

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

以下示例显示了如何使用@Commit注解:

@Commit (1)
@Test
public void testProcessWithoutRollback() {
    // ...
}
@Rollback

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

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

下面的示例使测试方法的结果不回滚(即,结果已提交到数据库):

@Rollback(false) (1)
@Test
public void testProcessWithoutRollback() {
    // ...
}
@BeforeTransaction

@BeforeTransaction表示已 Comments 的void方法应在事务启动之前运行,对于已配置为使用 Spring 的@TransactionalComments 在事务内运行的测试方法。从 Spring Framework 4.3 开始,@BeforeTransaction方法不需要为public,并且可以在基于 Java 8 的接口默认方法中声明。

以下示例显示了如何使用@BeforeTransaction注解:

@BeforeTransaction (1)
void beforeTransaction() {
    // logic to be executed before a transaction is started
}
@AfterTransaction

@AfterTransaction表示 Commentsvoid方法应在事务结束后运行,对于已配置为使用 Spring 的@TransactionalComments 在事务内运行的测试方法。从 Spring Framework 4.3 开始,@AfterTransaction方法不需要为public,并且可以在基于 Java 8 的接口默认方法中声明。

@AfterTransaction (1)
void afterTransaction() {
    // logic to be executed after a transaction has ended
}
@Sql

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

@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
public void userTest {
    // execute code that relies on the test schema and test data
}

有关更多详细信息,请参见使用@Sql 声明式执行 SQL 脚本

@SqlConfig

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

@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
public void userTest {
    // execute code that relies on the test data
}
@SqlGroup

@SqlGroup是一个容器 Comments,它聚合了几个@SqlComments。您可以本地使用@SqlGroup来声明多个嵌套的@SqlComments,也可以将其与 Java 8 对可重复 Comments 的支持结合使用,其中@Sql可以在同一类或方法上声明多次,从而隐式生成此容器 Comments。下面的示例显示如何声明一个 SQL 组:

@Test
@SqlGroup({ (1)
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
public void userTest {
    // execute code that uses the test schema and test data
}

3.4.2. 标准 Comments 支持

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

JSR-250 Lifecycle Annotations

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

如果测试类中的方法用@PostConstructComments,则该方法在基础测试框架的 before 方法之前运行(例如,用 JUnit Jupiter 的@BeforeEachComments 的方法),并且该方法适用于测试类中的每个测试方法。另一方面,如果测试类中的方法使用@PreDestroyComments,则该方法将永远不会运行。因此,在测试类中,建议您使用来自基础测试框架的测试生命周期回调,而不是@PostConstruct@PreDestroy

3.4.3. Spring JUnit 4 测试 Comments

以下 Comments 仅与SpringRunnerSpring 的 JUnit 4 规则Spring 的 JUnit 4 支持类结合使用时受支持:

@IfProfileValue

@IfProfileValue表示已为特定测试环境启用带 Comments 的测试。如果已配置的ProfileValueSource为提供的name返回匹配的value,则启用测试。否则,测试将被禁用,并且实际上将被忽略。

您可以在类级别,方法级别或两者上应用@IfProfileValue。对于该类或其子类中的任何方法,@IfProfileValue的类级用法优先于方法级用法。具体来说,如果在类级别和方法级别都启用了测试,则将启用该测试。缺少@IfProfileValue意味着测试已隐式启用。这类似于 JUnit 4 的@IgnoreComments 的语义,除了@Ignore的存在总是会禁用测试。

以下示例显示了带有@IfProfileValue注解的测试:

@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}

或者,您可以使用@IfProfileValue列表(具有OR语义)来配置@IfProfileValue,以实现对 JUnit 4 环境中的测试组的类似于 TestNG 的支持。考虑以下示例:

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

@ProfileValueSourceConfiguration是类级别的 Comments,它指定在检索通过@IfProfileValueComments 配置的配置文件值时要使用的ProfileValueSource类型。如果未为测试声明@ProfileValueSourceConfiguration,则默认使用SystemProfileValueSource。以下示例显示了如何使用@ProfileValueSourceConfiguration

@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
    // class body...
}
@Timed

@Timed表示带 Comments 的测试方法必须在指定的时间段(以毫秒为单位)内完成执行。如果文本执行时间超过指定的时间段,则测试将失败。

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

@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to execute
}

Spring 的@TimedComments 与 JUnit 4 的@Test(timeout=…)支持具有不同的语义。具体来说,由于 JUnit 4 处理测试执行超时的方式(即通过在单独的Thread中执行测试方法),如果测试花费的时间太长,@Test(timeout=…)将抢先失败。另一方面,Spring 的@Timed不会抢先通过测试,而是在失败之前 await 测试完成。

@Repeat

@Repeat表示必须重复运行带 Comments 的测试方法。Comments 中指定了要执行测试方法的次数。

重复执行的范围包括测试方法本身的执行以及测试夹具的任何安装或拆除。以下示例显示了如何使用@Repeat注解:

@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
    // ...
}

3.4.4. Spring JUnit Jupiter 测试 Comments

以下 Comments 仅在与SpringExtension和 JUnit Jupiter(即 JUnit 5 中的编程模型)结合使用时才受支持:

@SpringJUnitConfig

@SpringJUnitConfig是组合的 Comments,它将 JUnit Jupiter 的@ExtendWith(SpringExtension.class)和 Spring TestContext Framework 的@ContextConfiguration组合在一起。它可以在类级别用作@ContextConfiguration的替代品。关于配置选项,@ContextConfiguration@SpringJUnitConfig之间的唯一区别是可以使用@SpringJUnitConfig中的value属性声明带 Comments 的类。

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

@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}

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

@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}

有关更多详细信息,请参见Context Management以及@SpringJUnitConfig@ContextConfiguration的 javadoc。

@SpringJUnitWebConfig

@SpringJUnitWebConfig是一个组合 Comments,它将来自 JUnit Jupiter 的@ExtendWith(SpringExtension.class)与来自 Spring TestContext Framework 的@ContextConfiguration@WebAppConfiguration组合在一起。您可以在类级别使用它代替@ContextConfiguration@WebAppConfiguration。关于配置选项,@ContextConfiguration@SpringJUnitWebConfig之间的唯一区别是您可以使用@SpringJUnitWebConfig中的value属性声明带 Comments 的类 bu。此外,仅可以使用@SpringJUnitWebConfig中的resourcePath属性来覆盖@WebAppConfiguration中的value属性。

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

@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}

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

@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}

有关更多详细信息,请参见Context Management以及@SpringJUnitWebConfig@ContextConfiguration@WebAppConfiguration的 javadoc。

@EnabledIf

@EnabledIf用于表示已 Comments 的 JUnit Jupiter 测试类或测试方法已启用,如果提供的expression评估为true,则应运行@EnabledIf。具体来说,如果表达式的计算结果为Boolean.TRUE或等于trueString(忽略大小写),则启用测试。在类级别应用时,默认情况下也会自动启用该类中的所有测试方法。

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

但是请注意,不是属性占位符动态解析的结果的文本 Literals 的实际值为零,因为@EnabledIf("false")等效于@Disabled,而@EnabledIf("true")在逻辑上是没有意义的。

您可以使用@EnabledIf作为元 Comments 来创建自定义的组合 Comments。例如,您可以创建一个自定义@EnabledOnMacComments,如下所示:

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

@DisabledIf用于表示已 Comments 的 JUnit Jupiter 测试类或测试方法已禁用,并且如果提供的expression计算结果为true,则不应执行@DisabledIf。具体来说,如果表达式的计算结果为Boolean.TRUE或等于trueString(忽略大小写),则会禁用测试。当在类级别应用时,该类中的所有测试方法也会自动禁用。

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

但是请注意,不是属性占位符动态解析的结果的文本 Literals 的实际值为零,因为@DisabledIf("true")等效于@Disabled,而@DisabledIf("false")在逻辑上是没有意义的。

您可以使用@DisabledIf作为元 Comments 来创建自定义的组合 Comments。例如,您可以创建一个自定义@DisabledOnMacComments,如下所示:

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

3.4.5. 测试的元 Comments 支持

您可以将大多数与测试相关的 Comments 用作meta-annotations,以创建自定义的组合 Comments,并减少整个测试套件中的配置重复。

您可以将以下各项与TestContext framework一起用作元 Comments。

考虑以下示例:

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

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

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

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

然后,我们可以使用定制的@TransactionalDevTestConfigComments 来简化基于单个 JUnit 4 的测试类的配置,如下所示:

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

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

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

@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 的测试套件中重复上述配置,则可以通过引入一个自定义的组合 Comments 来减少重复,该 Comments 集中了 Spring 和 JUnit Jupiter 的通用测试配置,如下所示:

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

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

@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }

由于 JUnit Jupiter 支持将@Test@RepeatedTestParameterizedTest等作为元 Comments 使用,因此您也可以在测试方法级别创建自定义的组合 Comments。例如,如果我们希望创建一个组合的 Comments,将 JUnit Jupiter 的@Test@TagComments 与 Spring 的@TransactionalComments 相结合,则可以创建@TransactionalIntegrationTestComments,如下所示:

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

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

@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }

有关更多详细信息,请参见SpringComments 编程模型 Wiki 页面。

3.5. Spring TestContext 框架

Spring TestContext Framework(位于org.springframework.test.context包中)提供了由 Comments 驱动的通用单元和集成测试支持,这些支持与所使用的测试框架无关。 TestContext 框架还非常重视约定优于配置,您可以通过基于 Comments 的配置覆盖合理的默认值。

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

下一节概述了 TestContext 框架的内部。如果您只对使用框架感兴趣,而对使用自己的自定义侦听器或自定义加载程序进行扩展不感兴趣,请直接进入配置(context managementdependency injectiontransaction management),support classesannotation support部分。

3.5.1. 关键抽象

该框架的核心由TestContextManager类和TestContextTestExecutionListenerSmartContextLoader接口组成。为每个测试类创建一个TestContextManager(例如,用于在 JUnit Jupiter 中的单个测试类中执行所有测试方法)。 TestContextManager依次 Management 一个TestContext,该TestContext保留当前测试的上下文。 TestContextManager还随着测试的进行更新并委托TestExecutionListener实现来更新TestContext的状态,该实现通过提供依赖项注入,Management 事务等手段来检测实际的测试执行。 SmartContextLoader负责为给定的测试类加载ApplicationContext。有关更多信息和各种实现的示例,请参见javadoc和 Spring 测试套件。

TestContext

TestContext封装了要在其中执行测试的上下文(与所使用的实际测试框架无关),并为其负责的测试实例提供了上下文 Management 和缓存支持。 TestContext还委托SmartContextLoader来加载ApplicationContext(如果有要求)。

TestContextManager

TestContextManager是 Spring TestContext Framework 的主要入口点,并负责 Management 单个TestContext并在定义明确的测试执行点向每个注册的TestExecutionListener发 signal 通知事件:

TestExecutionListener

TestExecutionListener定义用于对注册侦听器的TestContextManager发布的测试执行事件做出反应的 API。参见TestExecutionListener Configuration

Context Loaders

ContextLoader是 Spring 2.5 中引入的策略接口,用于加载ApplicationContext以便由 Spring TestContext FrameworkManagement 的集成测试。您应该实现SmartContextLoader而不是此接口,以为带 Comments 的类,活动的 Bean 定义配置文件,测试属性源,上下文层次结构和WebApplicationContext提供支持。

SmartContextLoader是 Spring 3.1 中引入的ContextLoader接口的扩展。 SmartContextLoader SPI 取代了 Spring 2.5 中引入的ContextLoader SPI。具体来说,SmartContextLoader可以选择处理资源位置,带 Comments 的类或上下文初始化器。此外,SmartContextLoader可以在其加载的上下文中设置活动 bean 定义概要文件并测试属性源。

Spring 提供了以下实现:

3.5.2. 引导 TestContext 框架

Spring TestContext Framework 内部的默认配置足以满足所有常见用例。但是,有时开发团队或第三方框架希望更改默认的ContextLoader,实现自定义的TestContextContextCache,扩展默认的ContextCustomizerFactoryTestExecutionListener实现集,等等。为了对 TestContext 框架的运行方式进行低级控制,Spring 提供了自举策略。

TestContextBootstrapper定义了用于引导 TestContext 框架的 SPI。 TestContextManager使用TestContextBootstrapper来加载当前测试的TestExecutionListener实现并构建其 Management 的TestContext。您可以直接使用@BootstrapWith或作为元 Comments,为测试类(或测试类层次结构)配置自定义引导策略。如果没有通过使用@BootstrapWith显式配置引导程序,则根据@WebAppConfiguration的存在使用DefaultTestContextBootstrapperWebTestContextBootstrapper

由于TestContextBootstrapper SPI 将来可能会更改(以适应新要求),因此我们强烈建议实现者不要直接实现此接口,而应扩展AbstractTestContextBootstrapper或其具体子类之一。

3.5.3. TestExecutionListener 配置

Spring 提供了以下TestExecutionListener实现,这些实现默认情况下按以下 Sequences 注册:

注册自定义 TestExecutionListener 实现

您可以使用@TestExecutionListeners注解为测试类及其子类注册自定义TestExecutionListener实现。有关详细信息和示例,请参见annotation support@TestExecutionListeners的 javadoc。

自动发现默认的 TestExecutionListener 实现

通过使用@TestExecutionListeners注册自定义TestExecutionListener实现非常适合在有限的测试方案中使用的自定义侦听器。但是,如果需要在测试套件中使用自定义侦听器,则会变得很麻烦。从 Spring Framework 4.1 开始,通过支持通过SpringFactoriesLoader机制自动发现默认TestExecutionListener实现来解决此问题。

具体来说,spring-test模块在其META-INF/spring.factories属性文件的org.springframework.test.context.TestExecutionListener键下声明了所有核心默认的 TestExecutionListener+750++749+`实现贡献到默认侦听器列表中。

OrderTestExecutionListener 实现

当 TestContext 框架通过aforementioned SpringFactoriesLoader机制发现默认的TestExecutionListener实现时,实例化的侦听器将通过使用 Spring 的AnnotationAwareOrderComparator进行排序,该样式会使用 Spring 的Ordered接口和@OrderComments 进行排序。 AbstractTestExecutionListener和 Spring 提供的所有默认的TestExecutionListener实现都以适当的值实现Ordered。因此,第三方框架和开发人员应通过实现Ordered或声明@Order来确保按默认 Sequences 注册其默认的TestExecutionListener实现。有关为每个核心侦听器分配哪些值的详细信息,请参见 javadoc 以获取核心默认TestExecutionListener实现的getOrder()方法。

合并 TestExecutionListener 实现

如果通过@TestExecutionListeners注册了自定义TestExecutionListener,则不会注册默认侦听器。在大多数常见的测试方案中,这有效地迫使开发人员手动声明除任何自定义侦听器之外的所有默认侦听器。下面的 Lists 演示了这种配置样式:

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

这种方法的挑战在于,它要求开发人员确切地知道默认情况下已注册了哪些侦听器。此外,默认的侦听器集可以在各个发行版之间进行更改,例如,在 Spring Framework 4.1 中引入了SqlScriptsTestExecutionListener,在 Spring Framework 4.2 中引入了DirtiesContextBeforeModesTestExecutionListener。此外,像 Spring Security 这样的第三方框架通过使用上述自动发现机制来注册自己的默认TestExecutionListener实现。

为了避免必须了解并重新声明所有默认侦听器,可以将@TestExecutionListenersmergeMode属性设置为MergeMode.MERGE_WITH_DEFAULTSMERGE_WITH_DEFAULTS表示应将本地声明的侦听器与默认侦听器合并。合并算法可确保从列表中删除重复项,并确保根据AnnotationAwareOrderComparator的语义对合并的侦听器的结果集进行排序,如OrderTestExecutionListener 实现中所述。如果侦听器实现Ordered或带有@Order注解,则它可以影响将其与默认值合并的位置。否则,合并时,本地声明的侦听器将追加到默认侦听器列表中。

例如,如果上一个示例中的MyCustomTestExecutionListener类将其order值(例如500)配置为小于ServletTestExecutionListener(恰好是1000)的 Sequences,那么MyCustomTestExecutionListener可以自动与默认列表合并在ServletTestExecutionListener的前面,并且前面的示例可以替换为以下内容:

@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
public class MyTest {
    // class body...
}

3.5.4. 上下文 Management

每个TestContext为其负责的测试实例提供上下文 Management 和缓存支持。测试实例不会自动获得对配置的ApplicationContext的访问权限。但是,如果测试类实现ApplicationContextAware接口,则将对ApplicationContext的引用提供给测试实例。请注意,AbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTests实现ApplicationContextAware,因此自动提供对ApplicationContext的访问。

@Autowired ApplicationContext

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

@RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {

@Autowired (1)
private ApplicationContext applicationContext;

// class body...
}
  • (1) 注入ApplicationContext

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

@RunWith(SpringRunner.class)
@WebAppConfiguration (1)
@ContextConfiguration
public class MyWebAppTest {

@Autowired (2)
private WebApplicationContext wac;

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

通过@Autowired提供的依赖关系注入由DependencyInjectionTestExecutionListener提供,默认情况下已配置DependencyInjectionTestExecutionListener(请参见测试夹具的依赖注入)。

使用 TestContext 框架的测试类不需要扩展任何特定的类或实现特定的接口来配置其应用程序上下文。而是通过在类级别声明@ContextConfigurationComments 来实现配置。如果您的测试类未明确声明应用程序上下文资源位置或带 Comments 的类,则已配置的ContextLoader确定如何从默认位置或默认配置类加载上下文。除了上下文资源位置和带 Comments 的类,还可以通过应用程序上下文初始化程序配置应用程序上下文。

以下各节说明如何通过使用 XML 配置文件,Groovy 脚本,带 Comments 的类(通常为@Configuration类)或上下文初始化器,使用 Spring 的@ContextConfiguration注解配置测试ApplicationContext。另外,您可以针对高级用例实现并配置自己的自定义SmartContextLoader

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

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

@RunWith(SpringRunner.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)
public class MyTest {
    // class body...
}

@ContextConfiguration通过标准 Java value属性支持locations属性的别名。因此,如果不需要在@ContextConfiguration中声明其他属性,则可以使用以下示例中演示的速记格式,省略locations属性名称的声明并声明资源位置:

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
public class MyTest {
    // class body...
}

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

package com.example;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
public class MyTest {
    // class body...
}
使用 Groovy 脚本进行上下文配置

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

Enabling Groovy script support

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

下面的示例显示如何指定 Groovy 配置文件:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
public class MyTest {
    // class body...
}

如果您从@ContextConfiguration注解中省略了locationsvalue属性,则 TestContext 框架将尝试检测默认的 Groovy 脚本。具体来说,GenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoader根据测试类的名称检测默认位置。如果您的类名为com.example.MyTest,那么 Groovy 上下文加载器将从"classpath:com/example/MyTestContext.groovy"加载您的应用程序上下文。下面的示例演示如何使用默认值:

package com.example;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
public class MyTest {
    // class body...
}

Declaring XML configuration and Groovy scripts simultaneously

您可以使用@ContextConfigurationlocationsvalue属性同时声明 XML 配置文件和 Groovy 脚本。如果到已配置资源位置的路径以.xml结尾,则使用XmlBeanDefinitionReader加载它。否则,将使用GroovyBeanDefinitionReader加载它。

以下 Lists 显示了如何在集成测试中将两者结合在一起:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
public class MyTest {
// class body...
}
带 Comments 类的上下文配置

要使用带 Comments 的类(请参见基于 Java 的容器配置)为测试加载ApplicationContext,可以用@ContextConfigurationComments 测试类,并使用包含对带 Comments 的类的引用的数组来配置classes属性。以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
public class MyTest {
    // class body...
}

Annotated Classes

术语“带 Comments 的类”可以指以下任何一种:

  • @ConfigurationComments 的类。

  • 组件(即带有@Component@Service@Repository或其他构造型 Comments 的类)。

  • 带有javax.inject注解的 JSR-330 兼容类。

  • 包含@Bean个方法的任何其他类。

有关带 Comments 的类的配置和语义的更多信息,请参见@Configuration@Bean的 javadoc,尤其要注意@Bean Lite Mode 的讨论。

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

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
@ContextConfiguration (1)
public class OrderServiceTest {

    @Configuration
    static class Config {

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

    @Autowired
    private OrderService orderService;

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

}
混合 XML,Groovy 脚本和带 Comments 的类

有时可能需要混合使用 XML 配置文件,Groovy 脚本和带 Comments 的类(通常为@Configuration类)来为测试配置ApplicationContext。例如,如果在 Producing 使用 XML 配置,则可以决定要使用@Configuration类为测试配置特定的 Spring 托管组件,反之亦然。

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

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

使用上下文初始化程序进行上下文配置

要使用上下文初始化器为测试配置ApplicationContext,请使用@ContextConfigurationComments 测试类,并使用包含对实现ApplicationContextInitializer的类的引用的数组配置initializers属性。然后,使用声明的上下文初始值设定项来初始化为测试加载的ConfigurableApplicationContext。请注意,每个声明的初始化程序支持的具体ConfigurableApplicationContext类型必须与使用中的SmartContextLoader创建的ApplicationContext类型(通常为GenericApplicationContext)兼容。此外,初始化程序的调用 Sequences 取决于它们是实现 Spring 的Ordered接口还是用 Spring 的@OrderComments 或标准@PriorityComments 进行 Comments。下面的示例演示如何使用初始化程序:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) (1)
public class MyTest {
    // class body...
}

您也可以完全省略@ContextConfiguration中的 XML 配置文件,Groovy 脚本或带 Comments 的类的声明,而仅声明ApplicationContextInitializer类,然后这些类负责在上下文中注册 Bean(例如,通过编程方式从 XML 文件加载 Bean 定义或配置类。以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
public class MyTest {
    // class body...
}
上下文配置继承

@ContextConfiguration支持布尔值inheritLocationsinheritInitializers,它们指示是否应继承资源位置或超类声明的带 Comments 的类和上下文初始化器。这两个标志的默认值为true。这意味着测试类将继承资源位置或带 Comments 的类以及任何超类声明的上下文初始化器。具体地说,将测试类的资源位置或带 Comments 的类追加到超类声明的资源位置或带 Comments 的类的列表中。同样,将给定测试类的初始化程序添加到由测试超类定义的初始化程序集。因此,子类可以选择扩展资源位置,带 Comments 的类或上下文初始化器。

如果@ContextConfiguration中的inheritLocationsinheritInitializers属性设置为false,则用于测试类影子的资源位置或带 Comments 的类以及上下文初始化器分别有效地替换了由超类定义的配置。

在下一个使用 XML 资源位置的示例中,从base-config.xmlextended-config.xml依次加载ExtendedTestApplicationContext。因此,extended-config.xml中定义的 Bean 可以覆盖(即替换)base-config.xml中定义的 Bean。以下示例显示一个类如何扩展另一个类并使用其自己的配置文件和超类的配置文件:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
public 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)
public class ExtendedTest extends BaseTest {
    // class body...
}

同样,在下一个使用带 Comments 的类的示例中,从BaseConfigExtendedConfig类按该 Sequences 加载ExtendedTestApplicationContext。因此,ExtendedConfig中定义的 Bean 可以覆盖(即替换)BaseConfig中定义的 Bean。以下示例显示一个类如何扩展另一个类,并同时使用其自己的配置类和超类的配置类:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from BaseConfig
@ContextConfiguration(classes = BaseConfig.class) (1)
public class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@ContextConfiguration(classes = ExtendedConfig.class) (2)
public class ExtendedTest extends BaseTest {
    // class body...
}

在使用上下文初始化程序的下一个示例中,通过使用BaseInitializerExtendedInitializer初始化ExtendedTestApplicationContext。但是请注意,初始化程序的调用 Sequences 取决于它们是实现 Spring 的Ordered接口还是用 Spring 的@OrderComments 或标准@PriorityComments 进行 Comments。下面的示例显示一个类如何扩展另一个类,并同时使用其自己的初始化程序和超类的初始化程序:

@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by BaseInitializer
@ContextConfiguration(initializers = BaseInitializer.class) (1)
public class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@ContextConfiguration(initializers = ExtendedInitializer.class) (2)
public class ExtendedTest extends BaseTest {
    // class body...
}
使用环境配置文件进行上下文配置

Spring 3.1 在框架中引入了对环境和概要文件(AKA“ bean 定义概要文件”)概念的一流支持,并且可以配置集成测试以针对各种测试场景激活特定的 bean 定义概要文件。这可以通过用@ActiveProfilesComments 对测试类进行 Comments 并提供在加载ApplicationContext进行测试时应激活的配置文件列表来实现。

Note

您可以将@ActiveProfiles与新SmartContextLoader SPI 的任何实现一起使用,但是较旧的ContextLoader SPI 的实现不支持@ActiveProfiles

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

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

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

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

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

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

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

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

</beans>
package com.bank.service;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

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

运行TransferServiceTest时,其ApplicationContext是从 Classpath 根目录中的app-config.xml配置文件中加载的。如果检查app-config.xml,则可以看到accountRepository bean 对dataSource bean 有依赖性。但是,dataSource没有定义为顶级 bean。而是,dataSource定义了 3 次:在production配置文件中,在dev配置文件中和在default配置文件中。

通过用@ActiveProfiles("dev")CommentsTransferServiceTest,我们指示 Spring TestContext Framework 加载ApplicationContext并将活动配置文件设置为{"dev"}。结果,创建了一个嵌入式数据库,并用测试数据填充了该数据库,并通过对开发DataSource的引用来连接accountRepository bean。这可能是我们在集成测试中想要的。

将 bean 分配给default概要文件有时很有用。仅当没有专门激活其他配置文件时,才包含默认配置文件中的 Bean。您可以使用它来定义要在应用程序的默认状态下使用的“后备” bean。例如,您可以显式提供devproduction配置文件的数据源,但是当两者都不处于活动状态时,将内存中数据源定义为默认数据源。

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

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

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

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

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

    @Autowired DataSource dataSource;

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

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

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }

}
package com.bank.service;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

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

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

与基于 XML 的配置示例一样,我们仍然使用@ActiveProfiles("dev")CommentsTransferServiceTest,但是这次我们使用@ContextConfigurationComments 指定所有四个配置类。测试类的主体本身保持完全不变。

通常在给定项目中跨多个测试类使用一组概要文件。因此,为避免重复声明@ActiveProfiles注解,可以在 Base Class 上声明一次@ActiveProfiles,并且子类会自动从 Base Class 继承@ActiveProfiles配置。在以下示例中,@ActiveProfiles的声明(以及其他 Comments)已移至抽象超类AbstractIntegrationTest

package com.bank.service;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public abstract class AbstractIntegrationTest {
}
package com.bank.service;

// "dev" profile inherited from superclass
public class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    private TransferService transferService;

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

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

package com.bank.service;

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

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

要以编程方式解析活动 bean 定义概要文件,可以实现自定义ActiveProfilesResolver并通过使用@ActiveProfilesresolver属性对其进行注册。有关更多信息,请参见相应的javadoc。以下示例演示了如何实现和注册自定义OperatingSystemActiveProfilesResolver

package com.bank.service;

// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
    resolver = OperatingSystemActiveProfilesResolver.class,
    inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
package com.bank.service.test;

public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}
具有测试属性源的上下文配置

Spring 3.1 在框架中引入了对具有属性源层次结构的环境的概念的一流支持。从 Spring 4.1 开始,您可以使用特定于测试的属性源配置集成测试。与@Configuration类上使用的@PropertySource注解相反,您可以在测试类上声明@TestPropertySource注解,以声明测试属性文件或内联属性的资源位置。将这些测试属性源添加到Environment中的PropertySources集合中,以供为 Comments 的集成测试加载的ApplicationContext

Note

您可以将@TestPropertySourceSmartContextLoader SPI 的任何实现一起使用,但是较旧的ContextLoader SPI 的实现不支持@TestPropertySource

SmartContextLoader的实现通过MergedContextConfiguration中的getPropertySourceLocations()getPropertySourceProperties()方法访问合并的测试属性源值。

声明测试属性源

您可以使用@TestPropertySourcelocationsvalue属性来配置测试属性文件。

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

每个路径都被解释为 Spring Resource。纯路径(例如"test.properties")被视为相对于定义测试类的程序包的 Classpath 资源。以斜杠开头的路径被视为绝对 Classpath 资源(例如"/org/example/test.xml")。通过使用指定的资源协议来加载引用 URL 的路径(例如,以classpath:file:http:为前缀的路径)。不允许使用资源位置通配符(例如*/.properties):每个位置都必须精确评估一个.properties.xml资源。

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

@ContextConfiguration
@TestPropertySource("/test.properties") (1)
public class MyIntegrationTests {
    // class body...
}

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

键值对支持的语法与为 Java 属性文件中的条目定义的语法相同:

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

@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) (1)
public class MyIntegrationTests {
    // class body...
}
默认属性文件检测

如果@TestPropertySource被声明为空 Comments(即locationsproperties属性没有显式值),则会尝试检测相对于声明该 Comments 的类的默认属性文件。例如,如果带 Comments 的测试类是com.example.MyTest,则相应的默认属性文件是classpath:com/example/MyTest.properties。如果无法检测到默认值,则会引发IllegalStateException

Precedence

测试属性源的优先级高于从 os 环境,Java 系统属性或应用程序通过使用@PropertySource声明性地添加的属性源加载的属性。因此,测试属性源可用于选择性覆盖系统和应用程序属性源中定义的属性。此外,内联属性比从资源位置加载的属性具有更高的优先级。

在下一个示例中,timezoneport属性以及"/test.properties"中定义的任何属性都将覆盖系统和应用程序属性源中定义的同名属性。此外,如果"/test.properties"文件为timezoneport属性定义了条目,则这些条目将被使用properties属性声明的内联属性覆盖。以下示例显示如何在文件和内联中同时指定属性:

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

@TestPropertySource支持布尔值inheritLocationsinheritProperties,它们指示是否应继承属性文件和超类声明的内联属性的资源位置。这两个标志的默认值为true。这意味着测试类将继承任何超类声明的位置和内联属性。具体来说,将测试类的位置和内联属性附加到超类声明的位置和内联属性中。因此,子类可以选择扩展位置和内联属性。请注意,稍后出现的属性会 shade(即,覆盖)之前出现的相同名称的属性。此外,上述优先规则也适用于继承的测试属性源。

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

在下一个示例中,仅通过将base.properties文件用作测试属性源来加载ApplicationContext for BaseTest。相反,通过将base.propertiesextended.properties文件用作测试属性源位置来加载_5 的ApplicationContext。下面的示例显示如何通过使用properties文件在子类及其父类中定义属性:

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

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

在下一个示例中,仅使用内联的key1属性加载BaseTestApplicationContext。相反,使用内联的key1key2属性来加载_5 的ApplicationContext。下面的示例演示如何通过使用内联属性在子类及其父类中定义属性:

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

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
    // ...
}
加载 WebApplicationContext

Spring 3.2 引入了对在集成测试中加载WebApplicationContext的支持。要指示 TestContext 框架加载WebApplicationContext而不是标准ApplicationContext,可以用@WebAppConfigurationComments 各自的测试类。

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

请注意,Spring 对WebApplicationContext实现的测试支持与其对标准ApplicationContext实现的支持相当。使用WebApplicationContext进行测试时,可以通过使用@ContextConfiguration来声明 XML 配置文件,Groovy 脚本或@Configuration类。您还可以自由使用任何其他测试 Comments,例如@ActiveProfiles@TestExecutionListeners@Sql@Rollback等。

本节中的其余示例显示了用于加载WebApplicationContext的各种配置选项。以下示例显示了 TestContext 框架对配置约定的支持:

例子 1.约定

@RunWith(SpringRunner.class)

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

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

public class WacTests {
    //...
}

如果使用@WebAppConfigurationComments 测试类而未指定资源基本路径,则资源路径实际上默认为file:src/main/webapp。同样,如果您声明@ContextConfiguration而不指定资源locations,带 Comments 的classes或上下文initializers,则 Spring 会尝试使用约定(即WacTests-context.xmlWacTests类或静态嵌套@Configuration类放在同一包中)来检测配置的存在。

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

例子 2.默认资源语义

@RunWith(SpringRunner.class)

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

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")

public class WacTests {
    //...
}

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

下面的示例显示,我们可以通过指定 Spring 资源前缀来覆盖两个 Comments 的默认资源语义:

例子 3.显式资源语义

@RunWith(SpringRunner.class)

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

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

public class WacTests {
    //...
}

将本示例中的 Comments 与上一个示例进行对比。

使用网络模拟

为了提供全面的 Web 测试支持,Spring 3.2 引入了默认情况下启用的ServletTestExecutionListener。在针对WebApplicationContext进行测试时,此TestExecutionListener通过在每种测试方法之前使用 Spring Web 的RequestContextHolder来设置默认的线程本地状态,并根据@WebAppConfiguration配置的基本资源路径创建MockHttpServletRequestMockHttpServletResponseServletWebRequestServletTestExecutionListener还确保可以将MockHttpServletResponseServletWebRequest注入到测试实例中,并且一旦测试完成,它将清除线程本地状态。

加载WebApplicationContext进行测试后,您可能会发现需要与 Web 模拟进行交互,例如,设置测试夹具或在调用 Web 组件后执行声明。以下示例显示可以将哪些模拟自动连接到您的测试实例。请注意,WebApplicationContextMockServletContext都缓存在测试套件中,而其他模拟则由ServletTestExecutionListener按测试方法进行 Management。

例子 4.注入模拟

@WebAppConfiguration
@ContextConfiguration
public class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}
Context Caching

一旦 TestContext 框架为测试加载ApplicationContext(或WebApplicationContext),该上下文将被缓存并重新用于在同一测试套件中声明相同唯一上下文配置的所有后续测试。要了解缓存的工作原理,重要的是要了解“唯一”和“测试套件”的含义。

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

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

Test suites and forked processes

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

要从缓存机制中受益,所有测试必须在同一进程或测试套件中运行。这可以通过在 IDE 中以组的形式执行所有测试来实现。同样,在使用诸如 Ant,Maven 或 Gradle 之类的构建框架执行测试时,确保该构建框架不会在测试之间进行派生很重要。例如,如果将 Maven Surefire 插件的forkMode设置为alwayspertest,则 TestContext 框架将无法在测试类之间缓存应用程序上下文,因此,构建过程的运行速度将大大降低。

从 Spring Framework 4.3 开始,上下文缓存的大小限制为默认的最大大小 32.只要达到最大大小,就会使用最近最少使用(LRU)驱逐策略来驱逐和关闭陈旧的上下文。您可以通过设置名为spring.test.context.cache.maxSize的 JVM 系统属性,从命令行或构建脚本中配置最大大小。或者,您可以使用SpringProperties API 以编程方式设置相同的属性。

由于在给定的测试套件中加载大量应用程序上下文会导致该套件花费不必要的长时间执行,因此准确地知道已加载和缓存了多少个上下文通常是有益的。要查看基础上下文缓存的统计信息,可以将org.springframework.test.context.cache日志记录类别的日志级别设置为DEBUG

在不太可能的情况下,测试破坏了应用程序上下文并需要重新加载(例如,通过修改 bean 定义或应用程序对象的状态),可以用@DirtiesContextComments 测试类或测试方法(请参见@DirtiesContext的讨论) Spring 测试 Comments)。这指示 Spring 在运行下一个测试之前从缓存中删除上下文并重建应用程序上下文。请注意,DirtiesContextBeforeModesTestExecutionListenerDirtiesContextTestExecutionListener提供了对@DirtiesContextComments 的支持,默认情况下已启用它们。

Context Hierarchies

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

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

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

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

ControllerIntegrationTestspass 语句一个上下文层次结构来表示 Spring MVC Web 应用程序的典型集成测试场景,该上下文层次结构包含两个级别,一个层次用于根WebApplicationContext(使用TestAppConfig @Configuration类加载),一个层次用于调度程序 servlet WebApplicationContext(使用WebApplicationContext加载)。 WebConfig @Configuration类)。自动连接到测试实例中的WebApplicationContext是用于子上下文(即,层次结构中的最低上下文)的那个。以下 Lists 显示了此配置方案:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
public class ControllerIntegrationTests {

    @Autowired
    private WebApplicationContext wac;

    // ...
}

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

此示例中的测试类在测试类层次结构中定义了上下文层次结构。 AbstractWebTests在 Spring 支持的 Web 应用程序中声明根WebApplicationContext的配置。但是请注意,AbstractWebTests不声明@ContextHierarchy。因此,AbstractWebTests的子类可以选择参与上下文层次结构或遵循@ContextConfiguration的标准语义。 SoapWebServiceTestsRestWebServiceTests都扩展了AbstractWebTests并使用@ContextHierarchy定义了上下文层次结构。结果是加载了三个应用程序上下文(每个@ContextConfiguration的声明一个),并且基于AbstractWebTests中的配置加载的应用程序上下文被设置为为具体子类加载的每个上下文的父上下文。以下 Lists 显示了此配置方案:

@RunWith(SpringRunner.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 {}

具有合并上下文层次结构配置的类层次结构

本示例中的类显示了使用命名层次结构级别的目的,以便合并上下文层次结构中特定级别的配置。 BaseTests在层次结构中定义了两个级别parentchildExtendedTests扩展BaseTests并通过确保@ContextConfigurationname属性中声明的名称都为child来指示 Spring TestContext Framework 合并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"}加载的上下文的父上下文。以下 Lists 显示了此配置方案:

@RunWith(SpringRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
public class ExtendedTests extends BaseTests {}

具有覆盖的上下文层次结构配置的类层次结构

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

@RunWith(SpringRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
public class ExtendedTests extends BaseTests {}

Dirtying a context within a context hierarchy

如果在上下文被配置为上下文层次结构一部分的测试中使用@DirtiesContext,则可以使用hierarchyMode标志来控制如何清除上下文缓存。有关更多详细信息,请参见Spring 测试 Comments@DirtiesContext javadoc 中有关@DirtiesContext的讨论。

3.5.5. 测试夹具的依赖注入

使用DependencyInjectionTestExecutionListener(默认情况下已配置)时,测试实例的依赖项从使用@ContextConfiguration配置的应用程序上下文中的 bean 注入。您可以使用 setter 注入,字段注入或同时使用这两种方式,这取决于您选择的 Comments 以及是否将它们放置在 setter 方法或字段中。为了与 Spring 2.5 和 3.0 中引入的 Comments 支持保持一致,可以使用 Spring 的@AutowiredComments 或 JSR 330 中的@InjectComments。

Tip

TestContext 框架不检测测试实例的实例化方式。因此,对于构造函数使用@Autowired@Inject对测试类无效。

因为@Autowired用于执行按类型自动布线,所以如果您具有相同类型的多个 bean 定义,那么对于那些特定的 bean,您将不能依靠这种方法。在这种情况下,您可以将@Autowired@Qualifier结合使用。从 Spring 3.0 开始,您还可以选择将@Inject@Named结合使用。另外,如果您的测试类可以访问其ApplicationContext,则可以通过使用(例如)对applicationContext.getBean("titleRepository")的调用来执行显式查找。

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

请考虑测试Goals部分中概述的HibernateTitleRepository类的方案。接下来的两个代码 Lists 演示了在字段和 setter 方法上使用@Autowired的方法。在所有示例代码 Lists 之后显示了应用程序上下文配置。

Note

以下代码 Lists 中的依赖项注入行为并非特定于 JUnit4.相同的 DI 技术可以与任何测试框架结合使用。

以下示例调用了静态 assert 方法,例如assertNotNull(),但未在调用前加上Assert。在这种情况下,假定该方法已通过示例中未显示的import static声明正确导入。

第一个代码 Lists 显示了使用@Autowired进行字段注入的测试类的基于 JUnit 4 的实现:

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

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

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

或者,您可以将类配置为使用@Autowired进行 setter 注入,如下所示:

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

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

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

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

前面的代码 Lists 使用@ContextConfigurationComments(即repository-config.xml)引用的相同 XML 上下文文件。下面显示了此配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://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>

Note

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

// ...

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

// ...

指定的限定符值指示要注入的特定DataSource bean,从而将类型匹配的范围缩小到特定的 bean。其值与相应<bean>定义中的<qualifier>声明匹配。 Bean 名称用作后备限定符值,因此您也可以在该名称中有效地指向特定的 Bean(如先前所示,假设myDataSource是 Bean id)。

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

早年以来,Spring 就一直支持请求范围和会话范围的 bean。从 Spring 3.2 开始,您可以按照以下步骤测试请求范围和会话范围的 bean:

下一个代码片段显示了登录用例的 XML 配置。注意,userService bean 与请求范围内的loginAction bean 有依赖性。另外,通过使用SpEL expressions实例化LoginAction,该SpEL expressions从当前 HTTP 请求中检索用户名和密码。在我们的测试中,我们想通过 TestContext 框架 Management 的模拟来配置这些请求参数。以下 Lists 显示了此用例的配置:

例子 5.请求范围的 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(即被测对象)和MockHttpServletRequest都注入到我们的测试实例中。在requestScope()测试方法中,我们通过在提供的MockHttpServletRequest中设置请求参数来设置测试夹具。当在我们的userService上调用loginUser()方法时,我们可以确保用户服务可以访问当前MockHttpServletRequest的请求范围的loginAction(即,我们只在其中设置了参数)。然后,我们可以根据用户名和密码的已知 Importing 对结果进行 assert。以下 Lists 显示了如何执行此操作:

例子 6.请求范围的 bean 测试

@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

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

        LoginResults results = userService.loginUser();
        // assert results
    }
}

以下代码段类似于我们之前针对请求范围的 Bean 看到的代码段。但是,这次,userService bean 对会话范围的userPreferences bean 具有依赖性。注意,UserPreferences bean 是通过使用 SpEL 表达式实例化的,该表达式从当前 HTTP 会话中检索主题。在我们的测试中,我们需要在由 TestContext 框架 Management 的模拟会话中配置主题。以下示例显示了如何执行此操作:

例子 7.会话范围的 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()测试方法中,我们通过在提供的MockHttpSession中设置预期的theme属性来设置测试夹具。在userService上调用processUserPreferences()方法时,可以确保用户服务可以访问当前MockHttpSession的会话作用域userPreferences,并且可以基于配置的主题对结果执行 assert。以下示例显示了如何执行此操作:

例子 8.会话范围的 bean 测试

@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

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

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

3.5.7. TransactionManagement

在 TestContext 框架中,事务由TransactionalTestExecutionListenerManagement,默认情况下配置为TransactionalTestExecutionListener,即使您没有在测试类上显式声明@TestExecutionListeners。但是,要启用对事务的支持,必须在ApplicationContext中配置一个PlatformTransactionManager bean,该 Bean 加载了@ContextConfiguration语义(稍后将提供更多详细信息)。另外,必须在测试的类或方法级别声明 Spring 的@Transactional注解。

Test-managed Transactions

测试 Management 的事务是使用TransactionalTestExecutionListener声明式 Management 或使用TestTransaction以编程方式 Management 的事务(稍后描述)。您不应将此类事务与 Spring 托管的事务(由ApplicationContext加载以供测试直接由 Spring 直接 Management 的事务)或应用程序托管的事务(由测试调用的应用程序代码中以编程方式 Management 的事务)混淆。 SpringManagement 的事务和应用程序 Management 的事务通常参与测试 Management 的事务。但是,如果使用REQUIREDSUPPORTS以外的任何传播类型配置了 SpringManagement 的事务或应用程序 Management 的事务,则应格外小心(有关详细信息,请参见transaction propagation的讨论)。

启用和禁用 Transaction

@TransactionalComments 测试方法会导致测试在事务中运行,默认情况下,该事务在测试完成后会自动回滚。如果测试类用@TransactionalComments,则该类层次结构中的每个测试方法都在事务中运行。未用@TransactionalComments 的测试方法(在类或方法级别)不在事务内运行。此外,用@TransactionalComments 但将propagation类型设置为NOT_SUPPORTED的测试不在事务内运行。

请注意,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests已预先配置为在类级别提供事务支持。

下面的示例演示了为基于 Hibernate 的UserRepository编写集成测试的常见方案:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

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

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

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

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

事务回滚和提交行为中所述,在createUser()方法运行后无需清理数据库,因为TransactionalTestExecutionListener会自动回滚对数据库所做的任何更改。有关其他示例,请参见PetClinic Example

事务回滚和提交行为

默认情况下,测试 Transaction 将在测试完成后自动回滚;但是,可以通过@Commit@RollbackComments 声明性地配置事务提交和回滚行为。有关更多详细信息,请参见annotation support部分中的相应条目。

程序化 TransactionManagement

从 Spring Framework 4.1 开始,您可以使用TestTransaction中的静态方法以编程方式与测试 Management 的事务进行交互。例如,您可以在测试方法中,方法之前和之后使用TestTransaction来启动或结束当前的测试 Management 的事务,或配置当前的测试 Management 的事务以进行回滚或提交。启用TransactionalTestExecutionListener时,将自动提供对TestTransaction的支持。

以下示例演示了TestTransaction的某些功能。有关更多详细信息,请参见TestTransaction的 javadoc。

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

有时,您可能需要在事务测试方法之前或之后但在事务上下文之外执行某些代码。例如,在运行测试之前验证初始数据库状态或在测试运行之后验证预期的事务提交行为(如果测试已配置为提交事务)。 TransactionalTestExecutionListener完全支持这种情况的@BeforeTransaction@AfterTransaction注解。您可以使用这些 Comments 之一 Comments 测试类中的任何void方法或测试接口中的任何void默认方法,并且TransactionalTestExecutionListener确保您的事务之前或之后事务方法在适当的时间运行。

Tip

任何之前的方法(例如,以 JUnit Jupiter 的@BeforeEachComments 的方法)和任何之后的方法(例如以 JUnit Jupiter 的@AfterEachComments 的方法)都在事务内运行。此外,对于未配置为在事务内运行的测试方法,不会运行带有@BeforeTransaction@AfterTransactionComments 的方法。

配置事务 Management 器

TransactionalTestExecutionListener期望在 Spring ApplicationContext中为测试定义PlatformTransactionManager bean。如果测试的ApplicationContext中有PlatformTransactionManager的多个实例,则可以使用@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")声明限定符,或者TransactionManagementConfigurer可以由@Configuration类实现。有关在测试ApplicationContext中用于查找事务 Management 器的算法的详细信息,请查阅TestContextTransactionUtils.retrieveTransactionManager()的 Javadoc

演示所有与 Transaction 相关的 Comments

以下基于 JUnit 4 的示例显示了一个虚拟的集成测试方案,该方案突出显示了所有与事务相关的 Comments。该示例并不是为了演示最佳实践,而是为了演示如何使用这些 Comments。有关更多信息和配置示例,请参见annotation support部分。 @Sql 的事务 Management包含一个其他示例,该示例使用@Sql以默认事务回滚语义对声明性 SQL 脚本执行。以下示例以粗体显示了相关 Comments:

@RunWith(SpringRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "txMgr")
@Commit
public class FictitiousTransactionalTest {

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

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

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

    @After
    public void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

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

}

Avoid false positives when testing ORM code

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

// ...

@Autowired
SessionFactory sessionFactory;

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

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

// ...

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

// ...

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

// ...

3.5.8. 执行 SQL 脚本

在针对关系数据库编写集成测试时,执行 SQL 脚本来修改数据库架构或将测试数据插入表中通常是有益的。 spring-jdbc模块通过在加载 Spring ApplicationContext时执行 SQL 脚本,为“初始化”嵌入式或现有数据库提供支持。有关详情,请参见嵌入式数据库支持使用嵌入式数据库测试数据访问逻辑

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

以编程方式执行 SQL 脚本

Spring 提供了以下选项,用于在集成测试方法中以编程方式执行 SQL 脚本。

ScriptUtils提供了用于处理 SQL 脚本的静态 Util 方法的集合,并且主要供框架内部使用。但是,如果您需要完全控制 SQL 脚本的解析和执行方式,则ScriptUtils可能比稍后介绍的其他一些替代方法更适合您的需求。有关详细信息,请参见ScriptUtils中的javadoc

ResourceDatabasePopulator提供了基于对象的 API,可通过使用外部资源中定义的 SQL 脚本以编程方式填充,初始化或清理数据库。 ResourceDatabasePopulator提供了用于配置在解析和运行脚本时使用的字符编码,语句分隔符,Comments 定界符和错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参见javadoc。要运行ResourceDatabasePopulator中配置的脚本,可以调用populate(Connection)方法以针对java.sql.Connection执行填充程序,也可以调用execute(DataSource)方法以针对javax.sql.DataSource执行填充程序。以下示例为测试模式和测试数据指定 SQL 脚本,将语句分隔符设置为@@,并针对DataSource执行这些脚本:

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

请注意,ResourceDatabasePopulator内部委派给ScriptUtils来解析和运行 SQL 脚本。同样,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)方法内部使用ResourceDatabasePopulator运行 SQL 脚本。有关各种详细信息,请参见 javadoc 中的各种executeSqlScript(..)方法。

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

除了上述用于以编程方式运行 SQL 脚本的机制之外,您还可以在 Spring TestContext Framework 中声明性地配置 SQL 脚本。具体而言,您可以在测试类或测试方法上声明@Sql注解,以配置应该在集成测试方法之前或之后针对给定数据库运行的 SQL 脚本的资源路径。请注意,方法级别的声明将覆盖类级别的声明,并且SqlScriptsTestExecutionListener提供了对@Sql的支持,默认情况下已启用。

路径资源语义

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

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

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

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

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

如果未指定任何 SQL 脚本,则根据声明@Sql的位置来尝试检测default脚本。如果无法检测到默认值,则会引发IllegalStateException

声明多个@Sql 集

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

以下示例显示了如何将@Sql用作 Java 8 的可重复 Comments:

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
public void userTest {
    // execute code that uses the test schema and test data
}

在前面的示例中提出的方案中,test-schema.sql脚本对单行 Comments 使用了不同的语法。

以下示例与前面的示例相同,除了@Sql声明在@SqlGroup内分组在一起,以便与 Java 6 和 Java 7 兼容。

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

默认情况下,SQL 脚本在相应的测试方法之前执行。但是,如果需要在测试方法之后运行一组特定的脚本(例如,清理数据库状态),则可以使用@Sql中的executionPhase属性,如以下示例所示:

@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
)
public void userTest {
    // execute code that needs the test data to be committed
    // to the database outside of the test's transaction
}

请注意,分别从Sql.TransactionModeSql.ExecutionPhase静态导入ISOLATEDAFTER_TEST_METHOD

使用@SqlConfig 进行脚本配置

您可以使用@SqlConfig注解配置脚本解析和错误处理。当在集成测试类上声明为类级别的 Comments 时,@SqlConfig充当测试类层次结构中所有 SQL 脚本的全局配置。通过使用@Sql注解的config属性直接声明时,@SqlConfig用作封闭@Sql注解中声明的 SQL 脚本的本地配置。 @SqlConfig中的每个属性都有一个隐式默认值,该默认值记录在相应属性的 javadoc 中。不幸的是,由于 Java 语言规范中为 Comments 属性定义了规则,因此无法将null的值分配给 Comments 属性。因此,为了支持对继承的全局配置的覆盖,@SqlConfig属性的显式默认值为""(对于字符串)或DEFAULT(对于枚举)。这种方法允许@SqlConfig的局部声明通过提供""DEFAULT以外的值来选择性地覆盖@SqlConfig的全局声明中的各个属性。只要本地@SqlConfig属性未提供""DEFAULT以外的显式值,就会继承全局@SqlConfig属性。因此,显式本地配置将覆盖全局配置。

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

@Sql的 TransactionManagement

默认情况下,SqlScriptsTestExecutionListener为使用@Sql配置的脚本推断所需的事务语义。具体而言,根据transactionMode属性的配置值,SQL 脚本可以在没有事务的情况下运行,而可以在现有的 SpringManagement 的事务中运行(例如,由TransactionalTestExecutionListenerManagement 的事务以@TransactionalComments),也可以在隔离的事务中运行。在@SqlConfig中,并且在测试的ApplicationContext中存在PlatformTransactionManager。但是,作为最低要求,测试的ApplicationContext中必须存在javax.sql.DataSource

如果SqlScriptsTestExecutionListener用来检测DataSourcePlatformTransactionManager并推断事务语义的算法不符合您的需求,则可以通过设置@SqlConfigdataSourcetransactionManager属性来指定显式名称。此外,您可以通过设置@SqlConfigtransactionMode属性来控制事务传播行为(例如,是否应在隔离的事务中运行脚本)。尽管对@Sql事务 Management 的所有受支持选项的详尽讨论不在本参考手册的范围内,但是@SqlConfigSqlScriptsTestExecutionListener的 javadoc 提供了详细信息,并且以下示例显示了使用 JUnit Jupiter 和@Sql的事务测试的典型测试方案。 :

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

    final JdbcTemplate jdbcTemplate;

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

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

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

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}

请注意,运行usersTest()方法后无需清理数据库,因为对数据库所做的任何更改(在测试方法中或在/test-data.sql脚本中)都会由TransactionalTestExecutionListener自动回滚(有关详细信息,请参见transaction management)。 。

3.5.9. 并行测试执行

当使用 Spring TestContext Framework 时,Spring Framework 5.0 引入了对在单个 JVM 中并行执行测试的基本支持。通常,这意味着大多数测试类或测试方法可以并行执行,而无需更改测试代码或配置。

Tip

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

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

如果测试符合以下条件,请勿并行执行测试:

Tip

如果并行测试执行失败,并指出当前测试的ApplicationContext不再处于活动状态,则通常意味着ApplicationContext已从另一个线程中的ContextCache中删除。

这可能是由于使用@DirtiesContext或由于从ContextCache自动驱逐。如果@DirtiesContext是罪魁祸首,则您需要找到一种避免使用@DirtiesContext的方法,或者从并行执行中排除此类测试。如果已超过ContextCache的最大大小,则可以增加缓存的最大大小。有关详细信息,请参见context caching上的讨论。

Warning

仅当基础TestContext实现提供了副本构造函数时,才可以在 Spring TestContext Framework 中并行执行测试,如TestContext的 javadoc 中所述。 Spring 中使用的DefaultTestContext提供了这样的构造函数。但是,如果使用提供自定义TestContext实现的第三方库,则需要验证它是否适合并行测试执行。

3.5.10. TestContext Framework 支持类

本节描述了支持 Spring TestContext Framework 的各种类。

Spring JUnit 4 Runner

Spring TestContext Framework 通过自定义运行程序(在 JUnit 4.12 或更高版本上受支持)提供了与 JUnit 4 的完全集成。通过使用@RunWith(SpringJUnit4ClassRunner.class)或更短的@RunWith(SpringRunner.class)Comments 测试类,开发人员可以实现基于 JUnit 4 的标准单元测试和集成测试,并同时获得 TestContext 框架的好处,例如支持加载应用程序上下文,测试实例的依赖注入,事务性测试。方法执行,等等。如果您想将 Spring TestContext Framework 与备用运行程序(例如 JUnit 4 的Parameterized运行程序)或第三方运行程序(例如MockitoJUnitRunner)一起使用,则可以选择使用Spring 对 JUnit 规则的支持

以下代码 Lists 显示了配置测试类以与自定义 Spring Runner一起运行的最低要求:

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

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

在前面的示例中,@TestExecutionListeners配置有一个空列表以禁用默认侦听器,否则将需要通过@ContextConfiguration配置ApplicationContext

Spring JUnit 4 规则

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

SpringClassRule是支持 Spring TestContext Framework 的类级功能的 JUnit TestRule,而SpringMethodRule是支持 Spring TestContext Framework 的实例级和方法级的功能的 JUnit MethodRule

SpringRunner相比,Spring 的基于规则的 JUnit 支持具有独立于任何org.junit.runner.Runner实现的优点,因此可以与现有的替代运行程序(例如 JUnit 4 的Parameterized)或第三方运行程序(例如MockitoJUnitRunner )。

为了支持 TestContext 框架的全部功能,必须将SpringClassRuleSpringMethodRule组合在一起。以下示例显示了在集成测试中声明这些规则的正确方法:

// 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() {
        // execute test logic...
    }
}
JUnit 4 支持类

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

AbstractJUnit4SpringContextTests是抽象的基础测试类,它在 JUnit 4 环境中将 Spring TestContext Framework 与显式的ApplicationContext测试支持集成在一起。扩展AbstractJUnit4SpringContextTests时,可以访问protected applicationContext实例变量,可用于执行显式 bean 查找或测试整个上下文的状态。

AbstractTransactionalJUnit4SpringContextTestsAbstractJUnit4SpringContextTests的抽象事务扩展,它为 JDBC 访问添加了一些便利功能。此类期望在ApplicationContext中定义javax.sql.DataSource bean 和PlatformTransactionManager bean。扩展AbstractTransactionalJUnit4SpringContextTests时,可以访问protected jdbcTemplate实例变量,该实例变量可用于运行 SQL 语句来查询数据库。您可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,并且 Spring 确保此类查询在与应用程序代码相同的事务范围内运行。与 ORM 工具结合使用时,请务必避免使用false positives。如JDBC 测试支持中所述,AbstractTransactionalJUnit4SpringContextTests还提供了便捷方法,这些方法通过使用上述jdbcTemplate委派给JdbcTestUtils中的方法。此外,AbstractTransactionalJUnit4SpringContextTests提供了executeSqlScript(..)方法,用于针对已配置的DataSource运行 SQL 脚本。

Tip

这些类为扩展提供了便利。如果您不希望将测试类绑定到特定于 Spring 的类层次结构,则可以使用@RunWith(SpringRunner.class)Spring 的 JUnit 规则来配置自己的自定义测试类。

JUnit Jupiter 的 SpringExtension

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

此外,得益于 JUnit Jupiter 中丰富的扩展 API,Spring 可以提供除 Spring 支持的 JUnit 4 和 TestNG 功能集之外的以下功能:

以下代码 Lists 显示了如何配置测试类以将SpringExtension@ContextConfiguration结合使用:

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

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

由于您还可以将 JUnit 5 中的 Comments 用作元 Comments,因此 Spring 可以提供@SpringJUnitConfig@SpringJUnitWebConfig组成的 Comments,以简化测试ApplicationContext和 JUnit Jupiter 的配置。

以下示例使用@SpringJUnitConfig减少上一示例中使用的配置量:

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

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

同样,以下示例使用@SpringJUnitWebConfig创建一个WebApplicationContext以便与 JUnit Jupiter 一起使用:

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

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

有关更多详细信息,请参见Spring JUnit Jupiter 测试 Comments@SpringJUnitConfig@SpringJUnitWebConfig的文档。

使用 SpringExtension 进行依赖项注入

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

具体来说,SpringExtension可以将来自测试ApplicationContext的依赖项注入到以@BeforeAll@AfterAll@BeforeEach@AfterEach@Test@RepeatedTest@ParameterizedTest等标记的测试构造函数和方法中。

Constructor Injection

如果 JUnit Jupiter 测试类的构造函数中的参数类型为ApplicationContext(或其子类型),或者使用@Autowired@Qualifier@Value进行 Comments 或元 Comments,则 Spring 会为该特定参数的值注入相应的值。测试ApplicationContext中的 bean。如果所有参数都应由 Spring 提供,则也可以用@Autowired直接 Comments 测试构造函数。

Warning

如果测试类的构造函数本身带有@AutowiredComments,则 Spring 负责解析构造函数中的* all *参数。因此,在 JUnit Jupiter 中注册的其他ParameterResolver都无法解析此类构造函数的参数。

在以下示例中,Spring 将从TestConfig.class加载的ApplicationContext注入OrderService bean 到OrderServiceIntegrationTests构造函数中。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

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

    // tests that use the injected OrderService
}

请注意,此功能使测试依赖项为final,因此不可变。

Method Injection

如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数类型为ApplicationContext(或其子类型),或者使用@Autowired@Qualifier@Value进行 Comments 或元 Comments,则 Spring 会使用以下命令为该特定参数注入值测试的ApplicationContext对应的 bean。

在以下示例中,Spring 将从TestConfig.class加载的ApplicationContext注入OrderServicedeleteOrder()测试方法中:

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

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

由于 JUnit Jupiter 中ParameterResolver支持的强大功能,您不仅可以从 Spring 中而且从 JUnit Jupiter 本身或其他第三方扩展中也可以将多个依赖项注入到单个方法中。

下面的示例演示如何让 Spring 和 JUnit Jupiter 同时将依赖项注入到placeOrderRepeatedly()测试方法中。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

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

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

请注意,通过使用 JUnit Jupiter 中的@RepeatedTest,测试方法可以访问RepetitionInfo

TestNG 支持类

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

AbstractTestNGSpringContextTests是抽象的基础测试类,该类将 Spring TestContext Framework 与 TestNG 环境中的显式ApplicationContext测试支持集成在一起。扩展AbstractTestNGSpringContextTests时,可以访问protected applicationContext实例变量,可用于执行显式 bean 查找或测试整个上下文的状态。

AbstractTransactionalTestNGSpringContextTestsAbstractTestNGSpringContextTests的抽象事务扩展,它为 JDBC 访问添加了一些便利功能。此类期望在ApplicationContext中定义javax.sql.DataSource bean 和PlatformTransactionManager bean。扩展AbstractTransactionalTestNGSpringContextTests时,可以访问protected jdbcTemplate实例变量,该实例变量可用于执行 SQL 语句来查询数据库。您可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,并且 Spring 确保此类查询在与应用程序代码相同的事务范围内运行。与 ORM 工具结合使用时,请务必避免使用false positives。如JDBC 测试支持中所述,AbstractTransactionalTestNGSpringContextTests还提供了便捷方法,这些方法通过使用上述jdbcTemplate委派给JdbcTestUtils中的方法。此外,AbstractTransactionalTestNGSpringContextTests提供了executeSqlScript(..)方法,用于针对已配置的DataSource运行 SQL 脚本。

Tip

这些类为扩展提供了便利。如果您不希望将测试类绑定到特定于 Spring 的类层次结构,则可以使用@ContextConfiguration@TestExecutionListeners等来配置自己的自定义测试类,并使用TestContextManager手动检测您的测试类。有关如何检测您的测试类的示例,请参见AbstractTestNGSpringContextTests的源代码。

3.6. Spring MVC 测试框架

Spring MVC 测试框架提供了一流的支持,可以使用可与 JUnit,TestNG 或任何其他测试框架一起使用的流畅 API 测试 Spring MVC 代码。它是基于spring-test模块的Servlet API 模拟对象构建的,因此不使用正在运行的 Servlet 容器。它使用DispatcherServlet来提供完整的 Spring MVC 运行时行为,并支持通过 TestContext 框架加载实际的 Spring 配置以及独立模式,在该模式下,您可以手动实例化控制器并一次对其进行测试。

Spring MVC Test 还为使用RestTemplate的代码提供 Client 端支持。Client 端测试模拟服务器响应,并且不使用运行中的服务器。

Tip

Spring Boot 提供了一个选项,可以编写包括运行中的服务器在内的完整的端到端集成测试。如果这是您的目标,请参见Spring Boot 参考页。有关容器外测试与端到端集成测试之间的区别的更多信息,请参见容器外测试与端到端集成测试之间的差异

3.6.1. 服务器端测试

您可以使用 JUnit 或 TestNG 为 Spring MVC 控制器编写一个普通的单元测试。为此,实例化控制器,将其注入模拟或存根依赖性,然后调用其方法(根据需要传递MockHttpServletRequestMockHttpServletResponse和其他方法)。但是,在编写这样的单元测试时,仍有很多未经测试的内容:例如,请求 Map,数据绑定,类型转换,验证等等。此外,也可以调用其他控制器方法(例如@InitBinder@ModelAttribute@ExceptionHandler)作为请求处理生命周期的一部分。

Spring MVC Test 的目标是通过执行请求并通过实际的DispatcherServlet生成响应来提供一种测试控制器的有效方法。

Spring MVC Test 构建在spring-test模块中熟悉的Servlet API 的“模拟”实现的基础上。这允许执行请求和生成响应,而无需在 Servlet 容器中运行。在大多数情况下,一切都应像在运行时一样工作,但有一些值得注意的异常,如容器外测试与端到端集成测试之间的差异中所述。以下基于 JUnit Jupiter 的示例使用 Spring MVC Test:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {

    private MockMvc mockMvc;

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

    @Test
    void getAccount() throws Exception {
        this.mockMvc.perform(get("/accounts/1")
                .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json"))
            .andExpect(jsonPath("$.name").value("Lee"));
    }
}

前面的测试依赖于 TestContext 框架的WebApplicationContext支持从与测试类位于同一包中的 XML 配置文件中加载 Spring 配置,但是还支持基于 Java 和基于 Groovy 的配置。参见这些sample tests

MockMvc实例用于对/accounts/1执行GET请求,并验证结果响应的状态为 200,Content Type 为application/json,响应主体具有名为name的 JSON 属性,值为Lee。 Jayway JsonPath project支持jsonPath语法。本文档后面将讨论用于验证执行请求结果的许多其他选项。

Static Imports

示例preceding section中的 FluentAPI 需要一些静态导入,例如MockMvcRequestBuilders.*MockMvcResultMatchers.*MockMvcBuilders.*。查找这些类的一种简单方法是搜索与MockMvc*匹配的类型。如果使用 Eclipse 或基于 Eclipse 的 Spring Tool Suite,请确保在 Java→编辑器→Content Assist→收藏夹下的 Eclipse 首选项中将它们添加为“收藏的静态成员”。这样,您可以在键入静态方法名称的第一个字符后使用内容辅助。其他 IDE(例如 IntelliJ)可能不需要任何其他配置。检查对静态成员的代码完成支持。

Setup Choices

创建MockMvc实例有两个主要选项。第一种是通过 TestContext 框架加载 Spring MVC 配置,该框架加载 Spring 配置并将WebApplicationContext注入到测试中以用于构建MockMvc实例。以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("my-servlet-context.xml")
public class MyWebTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

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

    // ...

}

您的第二个选择是在不加载 Spring 配置的情况下手动创建控制器实例。而是自动创建基本的默认配置,该配置与 MVC JavaConfig 或 MVC 命名空间大致相当。您可以在一定程度上对其进行自定义。以下示例显示了如何执行此操作:

public class MyWebTests {

    private MockMvc mockMvc;

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

    // ...

}

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

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

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

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

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Autowired
    private AccountService accountService;

    // ...

}

另一方面,standaloneSetup稍微接近单元测试。它一次测试一个控制器。您可以手动注入具有模拟依赖项的控制器,并且不涉及加载 Spring 配置。这样的测试更多地集中在样式上,使查看被测试的控制器,是否需要任何特定的 Spring MVC 配置等工作变得更加容易。 standaloneSetup也是编写临时测试以验证特定行为或调试问题的一种非常方便的方法。

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

Setup Features

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

// 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();

此外,第三方框架(和应用程序)可以预先打包安装说明,例如MockMvcConfigurer中的那些。 Spring Framework 具有一个这样的内置实现,可以帮助保存和重用跨请求的 HTTP 会话。您可以按以下方式使用它:

// static import of SharedHttpSessionConfigurer.sharedHttpSession

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

// Use mockMvc to perform requests...

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

Performing Requests

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

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

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

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

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

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

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

mockMvc.perform(get("/hotels").param("thing", "somewhere"));

如果应用程序代码依赖 Servlet 请求参数并且没有显式检查查询字符串(通常是这种情况),那么使用哪个选项都没有关系。但是请记住,随 URI 模板提供的查询参数将被解码,而通过param(…)方法提供的请求参数应已被解码。

在大多数情况下,最好将上下文路径和 Servlet 路径保留在请求 URI 之外。如果必须使用完整的请求 URI 进行测试,请确保相应地设置contextPathservletPath,以便请求 Map 起作用,如以下示例所示:

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

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

public class MyWebTests {

    private MockMvc mockMvc;

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

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

Defining Expectations

您可以通过在执行请求后追加一个或多个.andExpect(..)调用来定义期望,如以下示例所示:

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

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

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

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

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

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

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

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

只要请求处理不会导致未处理的异常,print()方法就会将所有可用的结果数据打印到System.out。 Spring Framework 4.2 引入了log()方法和print()方法的两个其他变体,一个变量接受OutputStream,另一个变量接受Writer。例如,调用print(System.err)将结果数据打印到System.err,而调用print(myWriter)则将结果数据打印到自定义编写器。如果要记录结果数据而不是打印结果,则可以调用log()方法,该方法将结果数据记录为org.springframework.test.web.servlet.result记录类别下的一条DEBUG消息。

在某些情况下,您可能希望直接访问结果并验证否则无法验证的内容。可以通过在所有其他期望值之后附加.andReturn()来实现,如以下示例所示:

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

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

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

请注意,通常会应用共同的期望,并且在不创建单独的MockMvc实例的情况下不能将其覆盖。

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

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

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

Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Filter Registrations

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

mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();

已注册的过滤器通过spring-testMockFilterChain调用,最后一个过滤器委托给DispatcherServlet

容器外测试与端到端集成测试之间的区别

如前所述,Spring MVC Test 是基于spring-test模块的 Servlet API 模拟对象构建的,并且不使用正在运行的 Servlet 容器。因此,与在实际 Client 端和服务器上运行的完整端到端集成测试相比,存在一些重要差异。

考虑这一点的最简单方法是从空白MockHttpServletRequest开始。您添加到其中的内容就是请求的内容。可能令您感到惊讶的是,默认情况下没有上下文路径。没有jsessionid cookie;没有转发,错误或异步调度;因此,没有实际的 JSP 呈现。而是将“转发”和“重定向” URL 保存在MockHttpServletResponse中,并且可以按预期进行 assert。

这意味着,如果您使用 JSP,则可以验证将请求转发到的 JSP 页面,但不会呈现 HTML。换句话说,不调用 JSP。但是请注意,所有其他不依赖转发的呈现技术(例如 Thymeleaf 和 Freemarker)都可以按预期将 HTML 呈现到响应主体。通过@ResponseBody方法呈现 JSON,XML 和其他格式时也是如此。

或者,您可以考虑使用@WebIntegrationTest从 Spring Boot 获得完整的端到端集成测试支持。参见Spring Boot 参考指南

每种方法都各有利弊。从经典的单元测试到全面的集成测试,Spring MVC Test 中提供的选项在规模上是不同的。可以肯定的是,Spring MVC Test 中的所有选项都不属于经典单元测试的类别,但与之接近。例如,您可以通过将模拟服务注入到控制器中来隔离 Web 层,在这种情况下,您仅通过DispatcherServlet来测试 Web 层,但使用实际的 Spring 配置,因为您可能会与数据访问层隔离地对其之上的层进行测试。此外,您可以使用独立设置,一次只关注一个控制器,然后手动提供使其工作所需的配置。

使用 Spring MVC Test 时的另一个重要区别是,从概念上讲,此类测试是服务器端的,因此您可以检查使用了哪个处理程序,如果使用 HandlerExceptionResolver 处理了异常,则模型的内容是什么,绑定错误是什么?还有其他细节。这意味着编写期望值更容易,因为服务器不是一个黑盒子,就像通过实际的 HTTPClient 端对其进行测试时那样。通常,这是经典单元测试的一个优势:编写,推理和调试更容易,但不能代替完全集成测试的需要。同时,重要的是不要忽视以下事实:响应是最重要的检查对象。简而言之,即使在同一项目中,这里也存在多种测试样式和测试策略的空间。

其他服务器端测试示例

框架自己的测试包括许多 samples 测试,旨在展示如何使用 Spring MVC Test。您可以浏览这些示例以获取进一步的想法。另外,spring-mvc-showcase项目具有基于 Spring MVC Test 的完整测试范围。

3.6.2. HtmlUnit 集成

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

Note

MockMvc 使用不依赖 Servlet 容器的模板技术(例如 Thymeleaf,FreeMarker 等),但不适用于 JSP,因为它们依赖 Servlet 容器。

为什么要进行 HtmlUnit 集成?

想到的最明显的问题是“我为什么需要这个?”通过探索一个非常基本的示例应用程序,最好找到答案。假设您有一个 Spring MVC Web 应用程序,该应用程序支持对Message对象的 CRUD 操作。该应用程序还支持所有消息的分页。您将如何进行测试?

使用 Spring MVC Test,我们可以轻松地测试是否能够创建Message,如下所示:

MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));

如果我们要测试允许我们创建消息的表单视图怎么办?例如,假设我们的表单类似于以下代码段:

<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>

我们如何确保表单产生正确的请求以创建新消息?天真的尝试可能类似于以下内容:

mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());

此测试有一些明显的缺点。如果我们更新控制器以使用参数message而不是text,则即使 HTML 表单与控制器不同步,我们的表单测试也会 continue 通过。为了解决这个问题,我们可以结合以下两个测试:

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"));

这样可以减少测试不正确通过的风险,但是仍然存在一些问题:

总体问题是,测试网页不涉及单个交互。相反,它是用户如何与网页交互以及该网页与其他资源交互的组合。例如,表单视图的结果用作用户创建消息的 Importing。另外,我们的表单视图可以潜在地使用影响页面行为的其他资源,例如 JavaScript 验证。

对救援进行集成测试?

为了解决前面提到的问题,我们可以执行端到端集成测试,但这有一些缺点。考虑测试允许我们翻阅消息的视图。我们可能需要以下测试:

要设置这些测试,我们需要确保我们的数据库包含正确的消息。这带来了许多其他挑战:

这些挑战并不意味着我们应该完全放弃端到端集成测试。相反,我们可以通过重构详细的测试以使用运行速度更快,更可靠且没有副作用的模拟服务来减少端到端集成测试的数量。然后,我们可以实施少量 true 的端到端集成测试,以验证简单的工作流程,以确保一切正常工作。

ImportingHtmlUnit 集成

那么,如何在测试页面的交互性之间保持平衡,并在测试套件中保持良好的性能呢?答案是:“通过将 MockMvc 与 HtmlUnit 集成。”

HtmlUnit 集成选项

要将 MockMvc 与 HtmlUnit 集成时,有很多选择:

MockMvc 和 HtmlUnit

本节介绍如何集成 MockMvc 和 HtmlUnit。如果要使用原始 HtmlUnit 库,请使用此选项。

MockMvc 和 HtmlUnit 设置

首先,请确保您已包含对net.sourceforge.htmlunit:htmlunit的测试依赖项。为了将 HtmlUnit 与 Apache HttpComponents 4.5 一起使用,您需要使用 HtmlUnit 2.18 或更高版本。

我们可以使用MockMvcWebClientBuilder轻松创建一个与 MockMvc 集成的 HtmlUnit WebClient,如下所示:

@Autowired
WebApplicationContext context;

WebClient webClient;

@Before
public void setup() {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}

Note

这是使用MockMvcWebClientBuilder的简单示例。有关高级用法,请参见Advanced MockMvcWebClientBuilder

这样可以确保在服务器上引用localhost的所有 URL 都定向到我们的MockMvc实例,而无需 true 的 HTTP 连接。通常,通过使用网络连接来请求其他任何 URL。这使我们可以轻松测试 CDN 的使用。

MockMvc 和 HtmlUnit 的用法

现在,我们可以像往常一样使用 HtmlUnit,而无需将应用程序部署到 Servlet 容器。例如,我们可以请求视图创建以下消息:

HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");

Note

默认上下文路径为""。或者,我们可以指定上下文路径,如Advanced MockMvcWebClientBuilder中所述。

一旦有了对HtmlPage的引用,我们就可以填写表格并将其提交以创建一条消息,如以下示例所示:

HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();

最后,我们可以验证是否成功创建了新消息。以下 assert 使用AssertJ库:

assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");

前面的代码通过多种方式对我们的MockMvc test进行了改进。首先,我们不再需要显式验证表单,然后创建类似于表单的请求。相反,我们要求表单,将其填写并提交,从而大大减少了开销。

另一个重要因素是HtmlUnit 使用 Mozilla Rhino 引擎评估 JavaScript。这意味着我们还可以在页面内测试 JavaScript 的行为。

有关使用 HtmlUnit 的其他信息,请参见HtmlUnit documentation

Advanced MockMvcWebClientBuilder

在到目前为止的示例中,我们已通过 Spring TestContext Framework 为我们加载的WebApplicationContext构建一个WebClient,从而以最简单的方式使用了MockMvcWebClientBuilder。在以下示例中重复此方法:

@Autowired
WebApplicationContext context;

WebClient webClient;

@Before
public void setup() {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}

我们还可以指定其他配置选项,如以下示例所示:

WebClient webClient;

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

另外,我们可以通过分别配置MockMvc实例并将其提供给MockMvcWebClientBuilder来执行完全相同的设置,如下所示:

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();

这比较冗长,但是通过使用MockMvc实例构建WebClient,我们可以轻而易举地拥有 MockMvc 的全部功能。

Tip

有关创建MockMvc实例的其他信息,请参见Setup Choices

MockMvc 和 WebDriver

在前面的部分中,我们已经了解了如何将 MockMvc 与原始 HtmlUnit API 结合使用。在本节中,我们在 Selenium WebDriver中使用其他抽象使事情变得更加容易。

为什么要使用 WebDriver 和 MockMvc?

我们已经可以使用 HtmlUnit 和 MockMvc,那么为什么要使用 WebDriver? Selenium WebDriver 提供了一个非常优雅的 API,使我们可以轻松地组织代码。为了更好地显示其工作原理,我们在本节中探索一个示例。

Note

尽管是Selenium的一部分,WebDriver 并不需要 Selenium Server 来运行测试。

假设我们需要确保正确创建一条消息。测试涉及找到 HTML 表单 Importing 元素,将其填写并做出各种 assert。

这种方法会导致大量单独的测试,因为我们也想测试错误情况。例如,如果只填写表格的一部分,我们要确保得到一个错误。如果我们填写整个表格,那么新创建的消息将在之后显示。

如果将其中一个字段命名为“ summary”,则我们可能会在测试中的多个位置重复以下内容:

HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);

那么,如果我们将id更改为smmry会发生什么?这样做将迫使我们更新所有测试以纳入此更改。这违反了 DRY 原理,因此理想情况下,我们应将此代码提取到其自己的方法中,如下所示:

public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}

public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}

这样做可以确保我们在更改 UI 时不必更新所有测试。

我们甚至可以更进一步,并将此逻辑放在代表我们当前所在的HtmlPageObject内,如以下示例所示:

public class CreateMessagePage {

    final HtmlPage currentPage;

    final HtmlTextInput summaryInput;

    final HtmlSubmitInput submit;

    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }

    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);

        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);

        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }

    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }

    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}

以前,此模式称为页面对象模式。虽然我们当然可以使用 HtmlUnit 做到这一点,但 WebDriver 提供了一些我们在以下各节中探讨的工具,以使该模式的实现更加容易。

MockMvc 和 WebDriver 设置

要将 Selenium WebDriver 与 Spring MVC Test 框架一起使用,请确保您的项目包含对org.seleniumhq.selenium:selenium-htmlunit-driver的测试依赖项。

我们可以使用MockMvcHtmlUnitDriverBuilder轻松创建一个与 MockMvc 集成的 Selenium WebDriver,如以下示例所示:

@Autowired
    WebApplicationContext context;

    WebDriver driver;

    @Before
    public void setup() {
        driver = MockMvcHtmlUnitDriverBuilder
                .webAppContextSetup(context)
                .build();
}

Note

这是使用MockMvcHtmlUnitDriverBuilder的简单示例。有关更多高级用法,请参见Advanced MockMvcHtmlUnitDriverBuilder

前面的示例确保在服务器上引用localhost的所有 URL 都定向到我们的MockMvc实例,而无需 true 的 HTTP 连接。通常,通过使用网络连接来请求其他任何 URL。这使我们可以轻松测试 CDN 的使用。

MockMvc 和 WebDriver 的用法

现在,我们可以像往常一样使用 WebDriver,而无需将应用程序部署到 Servlet 容器。例如,我们可以请求视图创建以下消息:

CreateMessagePage page = CreateMessagePage.to(driver);

然后,我们可以填写表格并提交以创建一条消息,如下所示:

ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);

利用 Page Object Pattern,这可以改善HtmlUnit test的设计。正如我们在为什么要使用 WebDriver 和 MockMvc?中提到的,我们可以将 Page Object Pattern 与 HtmlUnit 一起使用,但使用 WebDriver 则容易得多。考虑以下CreateMessagePage实现:

public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;
    private WebElement text;

    (3)
    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}

最后,我们可以验证是否成功创建了新消息。以下 assert 使用AssertJassert 库:

assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");

我们可以看到ViewMessagePage使我们能够与自定义域模型进行交互。例如,它公开了一个返回Message对象的方法:

public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}

然后,我们可以在声明中使用富域对象。

最后,我们一定不要忘记在测试完成后关闭WebDriver实例,如下所示:

@After
public void destroy() {
    if (driver != null) {
        driver.close();
    }
}

有关使用 WebDriver 的其他信息,请参见 Selenium WebDriver documentation

Advanced MockMvcHtmlUnitDriverBuilder

在到目前为止的示例中,我们已通过 Spring TestContext Framework 为我们加载的WebApplicationContext构建一个WebDriver,从而以最简单的方式使用了MockMvcHtmlUnitDriverBuilder。在此重复此方法,如下所示:

@Autowired
WebApplicationContext context;

WebDriver driver;

@Before
public void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}

我们还可以指定其他配置选项,如下所示:

WebDriver driver;

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

另外,我们可以通过分别配置MockMvc实例并将其提供给MockMvcHtmlUnitDriverBuilder来执行完全相同的设置,如下所示:

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();

这比较冗长,但是通过使用MockMvc实例构建WebDriver,我们可以轻而易举地拥有 MockMvc 的全部功能。

Tip

有关创建MockMvc实例的其他信息,请参见Setup Choices

MockMvc 和 Geb

在上一节中,我们了解了如何在 WebDriver 中使用 MockMvc。在本节中,我们使用Geb来使我们的测试甚至更接近 Groovy-er。

为什么选择 Geb 和 MockMvc?

Geb 由 WebDriver 支持,因此它提供了许多从 WebDriver 获得的same benefits。但是,Geb 通过为我们处理一些样板代码使事情变得更加轻松。

MockMvc 和 Geb 设置

我们可以轻松地使用使用 MockMvc 的 Selenium WebDriver 来初始化 Geb Browser,如下所示:

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}

Note

这是使用MockMvcHtmlUnitDriverBuilder的简单示例。有关更多高级用法,请参见Advanced MockMvcHtmlUnitDriverBuilder

这样可以确保在服务器上引用localhost的所有 URL 都定向到我们的MockMvc实例,而无需 true 的 HTTP 连接。通常,通过使用网络连接来请求其他任何 URL。这使我们可以轻松测试 CDN 的使用。

MockMvc 和 Geb 的使用

现在,我们可以像往常一样使用 Geb 了,而无需将应用程序部署到 Servlet 容器中。例如,我们可以请求视图创建以下消息:

to CreateMessagePage

然后,我们可以填写表格并提交以创建一条消息,如下所示:

when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)

找不到的所有无法识别的方法调用或属性访问或引用都将转发到当前页面对象。这消除了我们直接使用 WebDriver 时需要的许多样板代码。

与直接使用 WebDriver 一样,这通过使用页面对象模式改进了HtmlUnit test的设计。如前所述,我们可以将页面对象模式与 HtmlUnit 和 WebDriver 一起使用,但使用 Geb 则更加容易。考虑我们新的基于 Groovy 的CreateMessagePage实现:

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

我们的CreateMessagePage扩展了Page。我们不会详细介绍Page,但是总而言之,它包含所有页面的通用功能。我们定义一个可在其中找到此页面的 URL。这使我们可以导航到页面,如下所示:

to CreateMessagePage

我们还有一个at闭包,它确定我们是否在指定的页面上。如果我们在正确的页面上,它应该返回true。这就是为什么我们可以 assert 我们在正确的页面上的原因,如下所示:

then:
at CreateMessagePage
errors.contains('This field is required.')

Note

我们在闭包中使用一个 assert,以便我们可以确定在错误的页面上哪里出错了。

接下来,我们创建一个content闭包,以指定页面内所有感兴趣的区域。我们可以使用jQuery-ish 导航器 API选择感兴趣的内容。

最后,我们可以验证是否成功创建了新消息,如下所示:

then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

有关如何充分利用 Geb 的更多详细信息,请参见奇书用户手册。

3.6.3. Client 端 REST 测试

您可以使用 Client 端测试来测试内部使用RestTemplate的代码。这个想法是声明预期的请求并提供“存根”响应,以便您可以专注于隔离测试代码(即,不运行服务器)。以下示例显示了如何执行此操作:

RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());

// Test code that uses the above RestTemplate ...

mockServer.verify();

在前面的示例中,MockRestServiceServer(Client 端 REST 测试的中心类)使用自定义ClientHttpRequestFactory配置RestTemplate,该自定义ClientHttpRequestFactoryassert 实际请求超出期望并返回“存根”响应。在这种情况下,我们希望有一个对/greeting的请求,并希望返回一个包含text/plain内容的 200 响应。我们可以根据需要定义其他预期的请求和存根响应。当我们定义期望的请求和存根响应时,RestTemplate可以照常在 Client 端代码中使用。在测试结束时,可以使用mockServer.verify()来验证是否满足所有期望。

默认情况下,请求应按声明的期望 Sequences 进行。您可以在构建服务器时设置ignoreExpectOrder选项,在这种情况下,将检查所有期望(以便)以找到给定请求的匹配项。这意味着允许请求以任何 Sequences 出现。以下示例使用ignoreExpectOrder

server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();

即使默认情况下无 Sequences 请求,每个请求也只能执行一次。 expect方法提供了一个重载的变量,该变量接受一个ExpectedCount参数,该参数指定一个计数范围(例如oncemanyTimesmaxminbetween等)。以下示例使用times

RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());

// ...

mockServer.verify();

请注意,当未设置ignoreExpectOrder时(默认设置),因此,请求应按声明 Sequences 进行,则该 Sequences 仅适用于任何预期请求中的第一个。例如,如果期望“/something”两次,然后是“/somewhere”三次,那么在请求“/somewhere”之前应该先请求“/something”,但是除了随后的“ /”东西”和“ /某处”,请求可以随时发出。

作为上述所有方法的替代,Client 端测试支持还提供ClientHttpRequestFactory实现,您可以将其配置为RestTemplate以将其绑定到MockMvc实例。这样就可以使用实际的服务器端逻辑来处理请求,而无需运行服务器。以下示例显示了如何执行此操作:

MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...
Static Imports

与服务器端测试一样,用于 Client 端测试的 FluentAPI 需要进行一些静态导入。通过搜索MockRest*可以轻松找到它们。 Eclipse 用户应在 Java→编辑器→Content Assist→收藏夹下的 Eclipse 首选项中将MockRestRequestMatchers.*MockRestResponseCreators.*作为“最喜欢的静态成员”添加。这样可以在键入静态方法名称的第一个字符后使用内容辅助。其他 IDE(例如 IntelliJ)可能不需要任何其他配置。检查对静态成员的代码完成的支持。

Client 端 REST 测试的更多示例

Spring MVC Test 自己的测试包括example tests个 Client 端 REST 测试。

3.7. WebTestClient

WebTestClient是围绕WebClient的薄壳,用于执行请求并公开专用的 FluentAPI 来验证响应。 WebTestClient通过使用模拟请求和响应绑定到 WebFlux 应用程序,或者它可以通过 HTTP 连接测试任何 Web 服务器。

Tip

Kotlin 用户:请参阅this sectionWebTestClient的使用有关。

3.7.1. Setup

要创建WebTestClient,您必须选择多个服务器设置选项之一。实际上,您是在配置要绑定到的 WebFlux 应用程序,还是使用 URL 连接到正在运行的服务器。

绑定到控制器

以下示例显示如何创建服务器设置以一次测试一个@Controller

client = WebTestClient.bindToController(new TestController()).build();

前面的示例加载WebFlux Java 配置并注册给定的控制器。通过使用模拟请求和响应对象,可以在没有 HTTP 服务器的情况下测试生成的 WebFlux 应用程序。构建器上有更多方法可以定制默认 WebFlux Java 配置。

绑定到 Router 功能

以下示例显示了如何从RouterFunction设置服务器:

RouterFunction<?> route = ...
    client = WebTestClient.bindToRouterFunction(route).build();

在内部,配置将传递到RouterFunctions.toWebHandler。通过使用模拟请求和响应对象,可以在没有 HTTP 服务器的情况下测试生成的 WebFlux 应用程序。

绑定到 ApplicationContext

以下示例显示了如何从应用程序的 Spring 配置或其一部分来设置服务器:

@RunWith(SpringRunner.class)
    @ContextConfiguration(classes = WebConfig.class) (1)
    public class MyTests {

        @Autowired
        private ApplicationContext context; (2)

        private WebTestClient client;

        @Before
        public void setUp() {
            client = WebTestClient.bindToApplicationContext(context).build(); (3)
        }
    }

在内部,配置将传递到WebHttpHandlerBuilder以构建请求处理链。有关更多详细信息,请参见WebHandler API。通过使用模拟请求和响应对象,可以在没有 HTTP 服务器的情况下测试生成的 WebFlux 应用程序。

绑定到服务器

以下服务器设置选项使您可以连接到正在运行的服务器:

client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
Client Builder

除了前面描述的服务器设置选项之外,您还可以配置 Client 端选项,包括基本 URL,默认 Headers,Client 端过滤器等。这些选项可在bindToServer之后使用。对于所有其他服务器,您需要使用configureClient()从服务器配置过渡到 Client 端配置,如下所示:

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

3.7.2. 写作测试

WebTestClient通过使用exchange()提供与WebClient相同的 API。 exchange()之后是链接的 API 工作流,用于验证响应。

通常,首先声明响应状态和 Headers,如下所示:

client.get().uri("/persons/1")
            .accept(MediaType.APPLICATION_JSON_UTF8)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
            // ...

然后,您指定如何解码和使用响应主体:

然后,您可以为主体使用内置的 assert。以下示例显示了一种方法:

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

您还可以超越内置的 assert 并创建自己的 assert,如以下示例所示:

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

您还可以退出工作流程并获得结果,如下所示:

EntityExchangeResult<Person> result = client.get().uri("/persons/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(Person.class)
            .returnResult();

Tip

当您需要使用泛型解码为目标类型时,请寻找接受ParameterizedTypeReference而不是Class<T>的重载方法。

No Content

如果响应中没有内容(或者您不在乎),请使用Void.class,以确保释放资源。以下示例显示了如何执行此操作:

client.get().uri("/persons/123")
            .exchange()
            .expectStatus().isNotFound()
            .expectBody(Void.class);

或者,如果要 assert 没有响应内容,则可以使用类似于以下内容的代码:

client.post().uri("/persons")
            .body(personMono, Person.class)
            .exchange()
            .expectStatus().isCreated()
            .expectBody().isEmpty();
JSON Content

使用expectBody()时,响应以byte[]的形式使用。这对于原始内容声明很有用。例如,您可以使用JSONAssert来验证 JSON 内容,如下所示:

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

您还可以使用JSONPath表达式,如下所示:

client.get().uri("/persons")
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$[0].name").isEqualTo("Jane")
            .jsonPath("$[1].name").isEqualTo("Jason");
Streaming Responses

要测试无限流(例如"text/event-stream""application/stream+json"),需要在响应状态和 Headers 声明之后立即退出链接的 API(通过使用returnResult),如以下示例所示:

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

现在,您可以使用Flux<T>,在它们到达时 assert 已解码的对象,然后在达到测试目标时在某个时候取消。我们建议使用reactor-test模块中的StepVerifier进行此操作,如以下示例所示:

Flux<Event> eventFux = result.getResponseBody();

    StepVerifier.create(eventFlux)
            .expectNext(person)
            .expectNextCount(4)
            .consumeNextWith(p -> ...)
            .thenCancel()
            .verify();
Request Body

当涉及到构建请求时,WebTestClient提供了与WebClient相同的 API,实现主要是简单的传递。有关如何准备带有正文的请求的示例,请参见WebClient documentation,包括提交表单数据,分段请求等。

3.8. PetClinic 示例

GitHub上提供的 PetClinic 应用程序显示了 JUnit 4 环境中 Spring TestContext Framework 的几个功能。大多数测试功能包含在AbstractClinicTests中,下面列出了部分功能:

import static org.junit.Assert.assertEquals;
// import ...

@ContextConfiguration (1)
public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests { (2)

    @Autowired (3)
    protected Clinic clinic;

    @Test
    public void getVets() {
        Collection<Vet> vets = this.clinic.getVets();
        assertEquals("JDBC query must show the same number of vets",
            super.countRowsInTable("VETS"), vets.size()); (4)
        Vet v1 = EntityUtils.getById(vets, Vet.class, 2);
        assertEquals("Leary", v1.getLastName());
        assertEquals(1, v1.getNrOfSpecialties());
        assertEquals("radiology", (v1.getSpecialties().get(0)).getName());
        // ...
    }

    // ...
}

像许多使用数据库的集成测试一样,AbstractClinicTests中的大多数测试取决于测试案例运行之前数据库中已存在的最小数据量。或者,您可以在测试用例设置的测试夹具中填充数据库(同样,在与测试相同的事务中)。

PetClinic 应用程序支持三种数据访问技术:JDBC,Hibernate 和 JPA。pass 语句@ContextConfiguration没有任何特定的资源位置,AbstractClinicTests类将从默认位置AbstractClinicTests-context.xml加载其应用程序上下文,该默认位置声明一个公共DataSource。子类指定必须声明PlatformTransactionManagerClinic的具体实现的其他上下文位置。

例如,PetClinic 测试的 Hibernate 实现包含以下实现。对于此示例,HibernateClinicTests不包含一行代码。我们只需要声明@ContextConfiguration,并且测试是从AbstractClinicTests继承的。因为声明@ContextConfiguration时没有任何特定的资源位置,所以 Spring TestContext Framework 从AbstractClinicTests-context.xml中定义的所有 bean(即继承位置)和HibernateClinicTests-context.xml加载应用程序上下文,而HibernateClinicTests-context.xml可能会覆盖AbstractClinicTests-context.xml中定义的 bean。以下 Lists 显示了HibernateClinicTests类的定义:

@ContextConfiguration (1)
public class HibernateClinicTests extends AbstractClinicTests { }

在大型应用程序中,Spring 配置通常分为多个文件。因此,通常在所有特定于应用程序的集成测试的通用 Base Class 中指定配置位置。这样的 Base Class 还可以添加有用的实例变量(自然而然地由 Dependency Injection 填充),例如在使用 Hibernate 的应用程序中为SessionFactory

集成测试中应尽可能具有与部署环境中完全相同的 Spring 配置文件。一个可能的不同点是数据库连接池和事务基础结构。如果要部署到功能完善的应用程序服务器,则可能使用其连接池(可通过 JNDI 获得)和 JTA 实现。因此,在 Producing,可以将JndiObjectFactoryBean<jee:jndi-lookup>用作DataSourceJtaTransactionManager。 JNDI 和 JTA 在容器外集成测试中不可用,因此您应使用 Commons DBCP BasicDataSourceDataSourceTransactionManagerHibernateTransactionManager之类的组合。您可以将此变异行为分解为一个 XML 文件,可以在应用程序服务器和“本地”配置之间进行选择,该配置与所有其他配置分开,在测试和生产环境之间不会有所不同。另外,我们建议您使用属性文件进行连接设置。有关示例,请参见 PetClinic 应用程序。

4.其他资源

有关测试的更多信息,请参见以下资源:

首页