15. 集成测试

15.1 概述

能够执行某些集成测试非常重要,无需部署到应用程序服务器或连接到其他企业基础结构。这将使您能够测试以下内容:

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

  • 使用JDBC或ORM工具进行数据访问。这将包括诸如SQL语句的正确性,Hibernate查询,JPA实体映射等。

Spring Framework为 spring-test 模块中的集成测试提供了一流的支持。实际JAR文件的名称可能包含发行版本,也可能采用长 org.springframework.test 形式,具体取决于您从何处获取(请参阅 section on Dependency Management 以获取解释)。该库包含 org.springframework.test 包,其中包含用于使用Spring容器进行集成测试的有 Value 的类。此测试不依赖于应用程序服务器或其他部署环境。这些测试比单元测试运行得慢,但比依赖部署到应用服务器的等效Selenium测试或远程测试快得多。

在Spring 2.5及更高版本中,单元和集成测试支持以注释驱动 Spring TestContext Framework 的形式提供。 TestContext框架与使用中的实际测试框架无关,因此允许在各种环境中进行测试,包括JUnit,TestNG等。

15.2 集成测试的目标

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

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

15.2.1 上下文管理和缓存

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

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

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

请参阅带有TestContext框架的 Section 15.5.4, “Context management”the section called “Context caching”

15.2.2 依赖注入测试夹具

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

作为示例,请考虑我们有一个类 HibernateTitleRepository 的场景,该类实现了 Title 域实体的数据访问逻辑。我们想编写测试以下方面的集成测试:

  • Spring配置:基本上,与 HibernateTitleRepository bean的配置相关的所有内容是否正确且存在?

  • Hibernate映射文件配置:是否正确映射了所有内容,并且是否具有正确的延迟加载设置?

  • HibernateTitleRepository 的逻辑:此类的已配置实例是否按预期执行?

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

15.2.3 Transaction 管理

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

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

如果您希望事务提交 - 异常,但在您希望特定测试填充或修改数据库时偶尔有用 - 可以指示TestContext框架使事务提交而不是通过 @Commit 注释回滚。

请参阅 TestContext framework 的事务管理。

15.2.4 支持集成测试的类

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

  • ApplicationContext ,用于执行显式bean查找或测试整个上下文的状态。

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

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

请参阅 TestContext framework 的支持类。

15.3 JDBC测试支持

org.springframework.test.jdbc 包中包含 JdbcTestUtils ,它是JDBC相关实用程序函数的集合,旨在简化标准数据库测试方案。具体来说, JdbcTestUtils 提供以下静态实用程序方法。

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

  • countRowsInTableWhere(..) :使用提供的 WHERE 子句计算给定表中的行数

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

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

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

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

spring-jdbc 模块支持配置和启动嵌入式数据库,该数据库可用于与数据库交互的集成测试。有关详细信息,请参阅 Section 19.8, “Embedded database support”Section 19.8.5, “Testing data access logic with an embedded database”

15.4 注释

15.4.1 spring 测试注释

Spring Framework提供了以下一组特定于Spring的注释,您可以在单元和集成测试中结合TestContext框架使用它们。有关更多信息,请参阅相应的javadoc,包括默认属性值,属性别名等。

@BootstrapWith

@BootstrapWith 是一个类级别注释,用于配置Spring TestContext Framework的引导方式。具体来说, @BootstrapWith 用于指定自定义 TestContextBootstrapper 。有关详细信息,请参阅 Bootstrapping the TestContext framework 部分。

@ContextConfiguration

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

资源位置通常是位于类路径中的XML配置文件或Groovy脚本;而注释类通常是 @Configuration 类。但是,资源位置也可以引用文件系统中的文件和脚本,带注释的类可以是组件类等。

@ContextConfiguration("/test-config.xml")
public class XmlApplicationContextTests {
    // class body...
}
@ContextConfiguration(classes = TestConfig.class)
public class ConfigClassApplicationContextTests {
    // class body...
}

作为声明资源位置或带注释的类的替代或补充, @ContextConfiguration 可用于声明 ApplicationContextInitializer 类。

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

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

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

@ContextConfiguration默认支持继承资源位置或配置类以及超类声明的上下文初始值设定项。

有关详细信息,请参阅 Section 15.5.4, “Context management”@ContextConfiguration javadocs。

@WebAppConfiguration

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

@ContextConfiguration
@WebAppConfiguration
public class WebAppTests {
    // class body...
}

要覆盖缺省值,请通过隐式 value 属性指定不同的基本资源路径。支持 classpath:file: 资源前缀。如果未提供资源前缀,则假定路径是文件系统资源。

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

请注意, @WebAppConfiguration 必须与 @ContextConfiguration 一起使用,可以在单个测试类中,也可以在测试类层次结构中使用。有关更多详细信息,请参阅 @WebAppConfiguration javadocs。

@ContextHierarchy

@ContextHierarchy 是一个类级别注释,用于为集成测试定义 ApplicationContext 的层次结构。应使用一个或多个 @ContextConfiguration 实例的列表声明 @ContextHierarchy ,每个实例定义上下文层次结构中的级别。以下示例演示了在单个测试类中使用 @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...
}

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

@ActiveProfiles

@ActiveProfiles 是一个类级别注释,用于在为集成测试加载 ApplicationContext 时声明哪些bean定义配置文件应处于活动状态。

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

@ActiveProfiles默认支持继承超类声明的活动bean定义配置文件。也可以通过实现自定义ActiveProfilesResolver并通过@ActiveProfiles的resolver属性注册它来以编程方式解析活动Bean定义配置文件。

有关示例和更多详细信息,请参阅 the section called “Context configuration with environment profiles”@ActiveProfiles javadocs。

@TestPropertySource

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

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

以下示例演示如何从类路径声明属性文件。

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

以下示例演示如何声明内联属性。

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

@DirtiesContext

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

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

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

  • 在当前测试类之前,在类模式设置为 BEFORE_CLASS 的类上声明。
@DirtiesContext(classMode = BEFORE_CLASS)
public class FreshContextTests {
    // some tests that require a new Spring container
}
  • 在当前测试类之后,在类模式设置为 AFTER_CLASS (即默认类模式)的类上声明。
@DirtiesContext
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
  • 在当前测试类中的每个测试方法之前,在类模式设置为 BEFORE_EACH_TEST_METHOD. 的类上声明
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
public class FreshContextTests {
    // some tests that require a new Spring container
}
  • 在当前测试类中的每个测试方法之后,在类模式设置为 AFTER_EACH_TEST_METHOD. 的类上声明
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
  • 在当前测试之前,在方法模式设置为 BEFORE_METHOD 的方法上声明。
@DirtiesContext(methodMode = BEFORE_METHOD)
@Test
public void testProcessWhichRequiresFreshAppCtx() {
    // some logic that requires a new Spring container
}
  • 在当前测试之后,在方法模式设置为 AFTER_METHOD (即默认方法模式)的方法上声明。
@DirtiesContext
@Test
public void testProcessWhichDirtiesAppCtx() {
    // some logic that results in the Spring container being dirtied
}

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

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

public class ExtendedTests extends BaseTests {

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

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

@TestExecutionListeners

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

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

@TestExecutionListeners 默认支持继承的侦听器。有关示例和更多详细信息,请参阅javadocs。

@Commit

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

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

@Rollback

@Rollback 指示在测试方法完成后是否应回滚事务测试方法的事务。如果 true ,则回滚事务;否则, Transaction 已提交(另见 @Commit )。即使没有显式声明 @Rollback ,Spring TestContext Framework中集成测试的回滚语义也默认为 true

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

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

@BeforeTransaction

@BeforeTransaction 表示在为通过Spring的 @Transactional 注释在事务中运行的测试方法启动事务之前,应该执行带注释的 void 方法。从Spring Framework 4.3开始, @BeforeTransaction 方法不需要 public ,并且可以在基于Java 8的接口默认方法上声明。

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

@AfterTransaction

@AfterTransaction 表示对于配置为通过Spring的 @Transactional 注释在事务中运行的测试方法,在事务结束后应该执行带注释的 void 方法。从Spring Framework 4.3开始, @AfterTransaction 方法不需要是 public ,并且可以在基于Java 8的接口默认方法上声明。

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

@Sql

@Sql 用于注释测试类或测试方法,以配置在集成测试期间针对给定数据库执行的SQL脚本。

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

有关详细信息,请参阅 the section called “Executing SQL scripts declaratively with @Sql”

@SqlConfig

@SqlConfig 定义了用于确定如何解析和执行通过 @Sql 注释配置的SQL脚本的元数据。

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

@SqlGroup

@SqlGroup 是一个容器注释,它聚合了几个 @Sql 注释。 @SqlGroup 可以原生使用,声明几个嵌套的 @Sql 注释,或者它可以与Java 8对可重复注释的支持结合使用,其中 @Sql 可以简单地在同一个类或方法上多次声明,隐式生成此容器注释。

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

15.4.2 标准注释支持

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

  • @Autowired

  • @Qualifier

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

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

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

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

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

  • @Required

  • @Transactional

在Spring TestContext框架中,@ PostConstruct和@PreDestroy可以与ApplicationContext中配置的任何应用程序组件一起使用标准语义;但是,这些生命周期注释在实际测试类中的使用受到限制。如果测试类中的方法使用@PostConstruct注释,那么该方法将在基础测试框架的任何before方法之前执行(例如,使用JUnit 4的@Before注释的方法),并且这将适用于测试中的每个测试方法类。另一方面,如果测试类中的方法使用@PreDestroy注释,则永远不会执行该方法。因此,在测试类中,建议使用来自底层测试框架的测试生命周期回调,而不是@PostConstruct和@PreDestroy。

15.4.3 Spring JUnit 4测试注释

仅当与 SpringRunnerSpring’s JUnit rulesSpring’s JUnit 4 support classes 一起使用时,才支持以下注释。

@IfProfileValue

@IfProfileValue 表示为特定测试环境启用了带注释的测试。如果配置的 ProfileValueSource 为提供的 name 返回匹配的 value ,则启用测试。否则,测试将被禁用并被有效忽略。

@IfProfileValue 可以在类级别,方法级别或两者中应用。 @IfProfileValue 的类级别使用优先于该类或其子类中的任何方法的方法级别使用。具体而言,如果在类级别和方法级别都启用了测试,则启用测试;没有 @IfProfileValue 意味着隐式启用了测试。这类似于JUnit 4的 @Ignore 注释的语义,除了 @Ignore 的存在总是禁用测试。

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

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

@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}

@ProfileValueSourceConfiguration

@ProfileValueSourceConfiguration 是一个类级注释,它指定在检索通过 @IfProfileValue 注释配置的配置文件值时要使用的 ProfileValueSource 的类型。如果未为测试声明 @ProfileValueSourceConfiguration ,则默认使用 SystemProfileValueSource

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

@Timed

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

时间段包括测试方法本身的执行,测试的任何重复(参见 @Repeat ),以及测试夹具的任何设置或拆除。

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

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

@Repeat

@Repeat 表示必须重复执行带注释的测试方法。在注释中指定测试方法的执行次数。

要重复的执行范围包括执行测试方法本身以及测试夹具的任何设置或拆除。

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

15.4.4 测试的元注释支持

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

以下每一项都可以与 TestContext framework 一起用作元注释。

  • @BootstrapWith

  • @ContextConfiguration

  • @ContextHierarchy

  • @ActiveProfiles

  • @TestPropertySource

  • @DirtiesContext

  • @WebAppConfiguration

  • @TestExecutionListeners

  • @Transactional

  • @BeforeTransaction

  • @AfterTransaction

  • @Commit

  • @Rollback

  • @Sql

  • @SqlConfig

  • @SqlGroup

  • @Repeat

  • @Timed

  • @IfProfileValue

  • @ProfileValueSourceConfiguration

例如,如果我们发现我们在基于JUnit 4的测试套件中重复以下配置......

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

我们可以通过引入自定义组合来减少上述重复注释集中了常见的测试配置,如下所示:

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

然后我们可以使用我们的自定义 @TransactionalDevTest 注释来简化各个测试类的配置,如下所示:

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

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

有关详细信息,请参阅 Spring Annotation Programming Model

15.5 Spring TestContext框架

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

除了通用测试基础结构之外,TestContext框架还以 abstract 支持类的形式为JUnit 4和TestNG提供显式支持。对于JUnit 4,Spring还提供了一个自定义的JUnit Runner 和自定义的JUnit Rules ,允许用户编写所谓的POJO测试类。 POJO测试类不需要扩展特定的类层次结构。

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

15.5.1 关键抽象

框架的核心由 TestContextManager 类和 TestContextTestExecutionListenerSmartContextLoader 接口组成。每个测试类创建一个 TestContextManager (例如,用于在JUnit 4中的单个测试类中执行所有测试方法)。 TestContextManager 反过来管理一个 TestContext ,它保存当前测试的上下文。 TestContextManager 还会在测试进行时更新 TestContext 的状态,并委托 TestExecutionListener 实现,它通过提供依赖注入,管理事务等来检测实际的测试执行情况。 SmartContextLoader 负责为给定的测试类加载 ApplicationContext 。有关各种实现的更多信息和示例,请参阅javadocs和Spring测试套件。

TestContext

TestContext 封装了执行测试的上下文,与使用中的实际测试框架无关,并为其负责的测试实例提供上下文管理和缓存支持。如果请求, TestContext 也会委托 SmartContextLoader 加载 ApplicationContext

TestContextManager

TestContextManager 是Spring TestContext Framework的主要入口点,它管理单个 TestContext 并在明确定义的测试执行点向每个注册的 TestExecutionListener 发送事件信号:

_009_在任何课前或特定测试框架的所有方法之前

  • 测试实例后处理

  • 在特定测试框架的每个方法之前或之前的任何方法之前

  • 在特定测试框架的每个方法之后或之后
    在任何课后或特定测试框架的所有方法之后

TestExecutionListener

TestExecutionListener 定义了用于响应测试监听器所注册的 TestContextManager 发布的执行事件的API。见 Section 15.5.3, “TestExecutionListener configuration”

上下文加载器

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

SmartContextLoader 是Spring 3.1中引入的 ContextLoader 接口的扩展。 SmartContextLoader SPI取代了Spring 2.5中引入的 ContextLoader SPI。具体来说, SmartContextLoader 可以选择处理资源 locations ,带注释的 classes 或上下文 initializers 。此外, SmartContextLoader 可以在其加载的上下文中设置活动Bean定义概要文件和测试属性源。

Spring提供以下实现:

  • DelegatingSmartContextLoader :两个默认加载器之一,它根据为测试类声明的配置或存在默认位置或默认配置类,在内部委托给 AnnotationConfigContextLoaderGenericXmlContextLoaderGenericGroovyXmlContextLoader 。只有Groovy在类路径上时才启用Groovy支持。

  • WebDelegatingSmartContextLoader :两个默认加载器之一,它根据为测试类声明的配置或存在默认位置或默认配置类,在内部委托给 AnnotationConfigWebContextLoaderGenericXmlWebContextLoaderGenericGroovyXmlWebContextLoader 。仅当测试类中存在 @WebAppConfiguration 时,才会使用Web ContextLoader 。只有Groovy在类路径上时才启用Groovy支持。

  • AnnotationConfigContextLoader :从带注释的类中加载标准 ApplicationContext

  • AnnotationConfigWebContextLoader :从注释中加载 WebApplicationContext 类。

  • GenericGroovyXmlContextLoader :从资源位置加载标准 ApplicationContext ,这些资源位置是Groovy脚本或XML配置文件。

  • GenericGroovyXmlWebContextLoader :从Groovy脚本或XML配置文件的资源位置加载 WebApplicationContext

  • GenericXmlContextLoader :从XML资源位置加载标准 ApplicationContext

  • GenericXmlWebContextLoader :从XML资源位置加载 WebApplicationContext

  • GenericPropertiesContextLoader :从Java Properties文件加载标准 ApplicationContext

15.5.2 引导TestContext框架

Spring TestContext Framework内部的默认配置足以满足所有常见用例。但是,有时候开发团队或第三方框架想要更改默认的 ContextLoader ,实现自定义 TestContextContextCache ,扩充默认的 ContextCustomizerFactoryTestExecutionListener 实现等等。对于TestContext框架的这种低级别控制操作,Spring提供了一个自举策略。

TestContextBootstrapper 定义了用于引导TestContext框架的SPI。 TestContextManager 使用 TestContextBootstrapper 来加载当前测试的 TestExecutionListener 实现并构建它管理的 TestContext 。可以通过 @BootstrapWith 直接或作为元注释为测试类(或测试类层次结构)配置自定义引导策略。如果未通过 @BootstrapWith 显式配置引导程序,则将使用 DefaultTestContextBootstrapperWebTestContextBootstrapper ,具体取决于 @WebAppConfiguration 的存在。

由于 TestContextBootstrapper SPI将来可能会发生变化以适应新的需求,因此强烈建议实施者不要直接实现此接口,而应扩展 AbstractTestContextBootstrapper 或其中一个具体的子类。

15.5.3 TestExecutionListener配置

Spring提供了以下默认注册的 TestExecutionListener 实现,完全按此顺序。

  • ServletTestExecutionListener :为 WebApplicationContext 配置Servlet API模拟

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

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

  • DirtiesContextTestExecutionListener :处理后模式的 @DirtiesContext 注释

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

  • SqlScriptsTestExecutionListener :执行通过 @Sql 注释配置的SQL脚本

注册自定义TestExecutionListeners

可以通过 @TestExecutionListeners 注释为测试类及其子类注册自定义 TestExecutionListener 。有关详细信息和示例,请参阅 annotation support@TestExecutionListeners 的javadoc。

自动发现默认的TestExecutionListeners

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

具体来说, spring-test 模块在其 META-INF/spring.factories 属性文件中的 org.springframework.test.context.TestExecutionListener 键下声明所有核心默认 TestExecutionListener 。第三方框架和开发人员可以通过自己的 META-INF/spring.factories 属性文件以相同的方式将自己的 TestExecutionListener 贡献给默认侦听器列表。

订购TestExecutionListeners

当TestContext框架通过前面提到的 SpringFactoriesLoader 机制发现默认的 TestExecutionListener 时,实例化的侦听器使用Spring的 AnnotationAwareOrderComparator 进行排序,它将Spring的 Ordered 接口和 @Order 注释用于排序。 AbstractTestExecutionListener 和Spring提供的所有默认 TestExecutionListener 都使用适当的值实现 Ordered 。因此,第三方框架和开发人员应确保通过实现 Ordered 或声明 @Order 以正确的顺序注册其默认 TestExecutionListener 。有关为每个核心侦听器分配的值的详细信息,请参阅javadocs以获取核心默认值 TestExecutionListenergetOrder() 方法。

合并TestExecutionListeners

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

@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这样的第三方框架通过上述的 automatic discovery mechanism 注册了自己的默认 TestExecutionListener

为了避免必须知道并重新声明 all 默认侦听器, @TestExecutionListenersmergeMode 属性可以设置为 MergeMode.MERGE_WITH_DEFAULTSMERGE_WITH_DEFAULTS 表示本地声明的侦听器应与默认侦听器合并。合并算法确保从列表中删除重复项,并根据 the section called “Ordering TestExecutionListeners” 中描述的 AnnotationAwareOrderComparator 的语义对生成的合并侦听器集进行排序。如果侦听器实现了 Ordered 或者使用 @Order 进行了注释,它可以影响它与默认值合并的位置;否则,本地声明的侦听器将在合并时简单地附加到默认侦听器列表中。

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

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

15.5.4 上下文管理

每个 TestContext 都为其负责的测试实例提供上下文管理和缓存支持。测试实例不会自动接收对已配置的 ApplicationContext 的访问权限。但是,如果测试类实现了 ApplicationContextAware 接口,则会向测试实例提供对 ApplicationContext 的引用。请注意 AbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTests 实现了 ApplicationContextAware ,因此可以自动提供对 ApplicationContext 的访问。

作为实现ApplicationContextAware接口的替代方法,您可以通过字段或setter方法上的@Autowired注释为测试类注入应用程序上下文。例如:@RunWith(SpringRunner.class)
@ContextConfiguration
公共课MyTest {

@Autowired
private ApplicationContext applicationContext;

//班主体......
同样,如果您的测试配置为加载WebApplicationContext,您可以将Web应用程序上下文注入到测试中,如下所示:@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
公共类MyWebAppTest {
@Autowired
private WebApplicationContext wac;

//班主体......
通过@Autowired进行的依赖注入由默认配置的DependencyInjectionTestExecutionListener提供(参见第15.5.5节“测试夹具的依赖注入”)。

使用TestContext框架的测试类不需要扩展任何特定类或实现特定接口来配置其应用程序上下文。相反,只需在类级别声明 @ContextConfiguration 注释即可实现配置。如果测试类未显式声明应用程序上下文资源 locations 或带注释的 classes ,则配置的 ContextLoader 将确定如何从默认位置或默认配置类加载上下文。除了上下文资源 locations 和带注释的 classes 之外,还可以通过应用程序上下文 initializers 配置应用程序上下文。

以下部分说明如何使用Spring的 @ContextConfiguration 注释配置 ApplicationContext via XML配置文件,Groovy脚本,带注释的类(通常为 @Configuration classes)或上下文初始值设定项。或者,您可以为高级用例实现和配置自己的自定义 SmartContextLoader

使用XML资源配置上下文

要使用XML配置文件为测试加载 ApplicationContext ,请使用 @ContextConfiguration 注释测试类,并使用包含XML配置元数据的资源位置的数组配置 locations 属性。普通路径或相对路径(例如 "context.xml" )将被视为相对于定义测试类的包的类路径资源。以斜杠开头的路径被视为绝对类路径位置,例如 "/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"})
public class MyTest {
    // class body...
}

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

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

如果省略 @ContextConfiguration 注释中的 locationsvalue 属性,TestContext框架将尝试检测默认的XML资源位置。具体来说, GenericXmlContextLoaderGenericXmlWebContextLoader 根据测试类的名称检测默认位置。如果您的类名为 com.example.MyTestGenericXmlContextLoader 将从 "classpath:com/example/MyTest-context.xml" 加载您的应用程序上下文。

package com.example;

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

使用Groovy脚本配置上下文

要使用使用 Groovy Bean Definition DSL 的Groovy脚本为测试加载 ApplicationContext ,请使用 @ContextConfiguration 注释测试类,并使用包含Groovy资源位置的数组配置 locationsvalue 属性脚本。 Groovy脚本的资源查找语义与 XML configuration files 中描述的相同。

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

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
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
public class MyTest {
    // class body...
}

可以通过@ContextConfiguration的locations或value属性同时声明XML配置文件和Groovy脚本。如果配置的资源位置的路径以.xml结尾,则将使用XmlBeanDefinitionReader加载;否则它将使用GroovyBeanDefinitionReader加载。以下清单演示了如何在集成测试中将两者结合使用。 @RunWith(SpringRunner.class)
//将从中加载ApplicationContext
//“/app-config.xml”和“/TestConfig.groovy”
@ContextConfiguration({“/ app-config.xml”,“/ TestConfig.groovy”})
公共课MyTest {
//班主体......
}

带注释类的上下文配置

要使用带注释的类(请参阅 Section 7.12, “Java-based container configuration” )为测试加载 ApplicationContext ,请使用 @ContextConfiguration 注释测试类,并使用包含对带注释类的引用的数组配置 classes 属性。

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

术语带注释的类可以引用以下任何一种。用@Configuration注释的类一个组件(即用@ Component,@ Service,@ Repository等注释的类)一个JSR-330兼容类,用javax.inject注释注释任何其他包含@ Bean方法的类有关注释类的配置和语义的更多信息,请参阅@Configuration和@Bean的javadocs,特别注意对@ BeanLite模式的讨论。

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

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
@ContextConfiguration
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脚本和带注释的类

有时可能需要混合XML配置文件,Groovy脚本和带注释的类(即,通常为 @Configuration 类)来为您的测试配置 ApplicationContext 。例如,如果在 生产环境 中使用XML配置,则可能决定要使用 @Configuration 类为测试配置特定的Spring管理组件,反之亦然。

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

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

上下文配置上下文初始化器

要使用上下文初始化程序为测试配置 ApplicationContext ,请使用 @ContextConfiguration 注释测试类,并使用包含对实现 ApplicationContextInitializer 的类的引用的数组配置 initializers 属性。然后,声明的上下文初始值设定项将用于初始化为测试加载的 ConfigurableApplicationContext 。请注意,每个声明的初始值设定项支持的具体 ConfigurableApplicationContext 类型必须与正在使用的 SmartContextLoader 创建的 ApplicationContext 类型兼容(即,通常为 GenericApplicationContext )。此外,调用初始值设定项的顺序取决于它们是实现Spring的 Ordered 接口还是使用Spring的 @Order 注释或标准 @Priority 注释进行注释。

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

也可以完全省略 @ContextConfiguration 中的XML配置文件,Groovy脚本或带注释的类的声明,而是仅声明 ApplicationContextInitializer 类,然后负责在上下文中注册bean - 例如,通过以编程方式从XML文件加载bean定义或配置类。

@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class)
public class MyTest {
    // class body...
}

上下文配置继承

@ContextConfiguration 支持boolean inheritLocationsinheritInitializers 属性,这些属性表示是否应继承超类声明的资源位置或带注释的类和上下文初始值设定项。两个标志的默认值是 true 。这意味着测试类继承资源位置或带注释的类以及任何超类声明的上下文初始值设定项。具体而言,测试类的资源位置或带注释的类将附加到超类声明的资源位置列表或带注释的类。类似地,给定测试类的初始值设定项将添加到由测试超类定义的初始化程序集中。因此,子类可以选择扩展资源位置,带注释的类或上下文初始化器。

如果 @ContextConfiguration 中的 inheritLocationsinheritInitializers 属性设置为 false ,则资源位置或带注释的类和上下文初始化程序分别为测试类阴影并有效替换超类定义的配置。

在以下使用XML资源位置的示例中, ApplicationContext for ExtendedTest 将按此顺序从 "base-config.xml" 和 "extended-config.xml" 加载。因此, "extended-config.xml" 中定义的Bean可能会覆盖(即替换) "base-config.xml" 中定义的那些。

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

同样,在以下使用带注释的类的示例中, ApplicationContext for ExtendedTest 将按此顺序从 BaseConfigExtendedConfig 类加载。因此, ExtendedConfig 中定义的Bean可能会覆盖(即替换) BaseConfig 中定义的那些。

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

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

在以下使用上下文初始值设定项的示例中,将使用 BaseInitializerExtendedInitializer 初始化 ApplicationContext for ExtendedTest 。但请注意,调用初始值设定项的顺序取决于它们是实现Spring的 Ordered 接口还是使用Spring的 @Order 注释或标准 @Priority 注释进行注释。

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

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@ContextConfiguration(initializers = ExtendedInitializer.class)
public class ExtendedTest extends BaseTest {
    // class body...
}

具有环境配置文件的上下文配置

Spring 3.1在框架中引入了环境和配置文件(a.k.a.,bean定义配置文件)概念的一流支持,并且可以配置集成测试以激活各种测试场景的特定bean定义配置文件。这是通过使用 @ActiveProfiles 注释注释测试类并提供在加载测试的 ApplicationContext 时应激活的配置文件列表来实现的。

@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 时,将从类路径根目录中的 app-config.xml 配置文件加载 ApplicationContext 。如果你检查 app-config.xml ,你会注意到 accountRepository bean依赖于 dataSource bean;但是, dataSource 未定义为顶级bean。相反, dataSource 定义了三次:在 生产环境 配置文件,开发配置文件和默认配置文件中。

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

将bean分配给 default 配置文件有时很有用。仅当未专门激活其他配置文件时,才会包含默认配置文件中的Bean。这可用于定义要在应用程序的默认状态中使用的回退bean。例如,您可以明确提供 devproduction 配置文件的数据源,但在这些配置文件都不活动时,将内存数据源定义为默认值。

以下代码清单演示了如何实现相同的配置和集成测试,但使用 @Configuration classes而不是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 类:

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

  • StandaloneDataConfig :为适合开发人员测试的嵌入式数据库定义 dataSource

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

  • DefaultDataConfig :为没有配置文件处于活动状态的默认嵌入式数据库定义 dataSource

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

通常情况下,在给定项目中的多个测试类中使用单组配置文件。因此,为了避免 @ActiveProfiles 注释的重复声明,可以在基类上声明 @ActiveProfiles 一次,并且子类将自动从基类继承 @ActiveProfiles 配置。在以下示例中, @ActiveProfiles (以及其他注释)的声明已移至抽象超类 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 属性注册它。以下示例演示如何实现和注册自定义 OperatingSystemActiveProfilesResolver 。有关详细信息,请参阅相应的javadoc。

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 注释,以声明测试属性文件或内联属性的资源位置。这些测试属性源将被添加到 EnvironmentPropertySources 的集合中,用于为注释集成测试加载的 ApplicationContext

@TestPropertySource可以与SmartContextLoader SPI的任何实现一起使用,但旧版ContextLoader SPI的实现不支持@TestPropertySource。 SmartContextLoader的实现通过MergedContextConfiguration中的getPropertySourceLocations()和getPropertySourceProperties()方法获得对合并的测试属性源值的访问。

Declaring test property sources

可以通过 @TestPropertySourcelocationsvalue 属性配置测试属性文件,如以下示例所示。

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

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

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

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

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

  • "key=value"

  • "key:value"

  • "key value"

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

Default properties file detection

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

Precedence

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

在以下示例中, timezoneport 属性以及 "/test.properties" 中定义的任何属性将覆盖在系统和应用程序属性源中定义的同名属性。此外,如果 "/test.properties" 文件定义了 timezoneport 属性的条目,那么这些属性将被通过 properties 属性声明的内联属性覆盖。

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

Inheriting and overriding test property sources

@TestPropertySource 支持boolean inheritLocationsinheritProperties 属性,这些属性表示是否应继承属性文件的资源位置和超类声明的内联属性。两个标志的默认值是 true 。这意味着测试类继承了任何超类声明的位置和内联属性。具体而言,测试类的位置和内联属性将附加到超类声明的位置和内联属性。因此,子类可以选择扩展位置和内联属性。请注意,稍后出现的属性将隐藏(即,覆盖)先前出现的同名属性。此外,上述优先规则也适用于继承的测试属性源。

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

在以下示例中,将仅使用 "base.properties" 文件作为测试属性源加载 ApplicationContext for BaseTest 。相反, ExtendedTest for ExtendedTest 将使用 "base.properties" and "extended.properties" 文件作为测试属性源位置加载。

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

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

在以下示例中,将仅使用内联 key1 属性加载 ApplicationContext for BaseTest 。相反, ApplicationContext for ExtendedTest 将使用内联 key1key2 属性加载。

@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 ,只需使用 @WebAppConfiguration 注释相应的测试类。

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

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

以下示例演示了一些用于加载 WebApplicationContext 的各种配置选项。

Conventions.

@RunWith(SpringRunner.class)

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

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

public class WacTests {
    //...
}

上面的示例演示了TestContext框架对约定优于配置的支持。如果使用 @WebAppConfiguration 注释测试类而未指定资源基路径,则资源路径将有效地默认为 "file:src/main/webapp" 。类似地,如果声明 @ContextConfiguration 而未指定资源 locations ,带注释的 classes 或上下文 initializers ,Spring将尝试使用约定检测配置的存在(即 "WacTests-context.xml" 在与 WacTests 类相同的包中或静态嵌套的 @Configuration 类中)。

Default resource semantics.

@RunWith(SpringRunner.class)

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

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

public class WacTests {
    //...
}

此示例演示如何使用 @WebAppConfiguration 显式声明资源基路径,使用 @ContextConfiguration 显示XML资源位置。这里要注意的重要一点是不同的语义带有这两个注释的路径。默认情况下, @WebAppConfiguration 资源路径是基于文件系统的;而 @ContextConfiguration 资源位置是基于类路径的。

Explicit resource semantics.

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

在第三个示例中,我们看到我们可以通过指定Spring资源前缀来覆盖两个注释的默认资源语义。将此示例中的注释与前一个示例进行对比。

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

一旦为测试加载 WebApplicationContext ,您可能会发现需要与Web模拟进行交互 - 例如,设置测试夹具或在调用Web组件后执行断言。以下示例演示了哪些模拟可以自动装配到测试实例中。请注意, WebApplicationContextMockServletContext 都在测试套件中缓存;然而,其他模拟由 ServletTestExecutionListener 按照测试方法进行管理。

Injecting mocks.

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

    //...
}

上下文缓存

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

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

  • locations (来自@ContextConfiguration)

  • classes (来自@ContextConfiguration)

  • contextInitializerClasses (来自@ContextConfiguration)

  • contextCustomizers (来自ContextCustomizerFactory)

  • contextLoader (来自@ContextConfiguration)

  • parent (来自@ContextHierarchy)

  • activeProfiles (来自@ActiveProfiles)

  • propertySourceLocations (来自@TestPropertySource)

  • propertySourceProperties (来自@TestPropertySource)

  • resourceBasePath (来自@WebAppConfiguration)

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

Spring TestContext框架将应用程序上下文存储在静态缓存中。这意味着上下文实际上存储在静态变量中。换句话说,如果测试在单独的进程中执行,则静态高速缓存将在每次测试执行之间被清除,这将有效地禁用高速缓存机制。要从缓存机制中受益,所有测试必须在同一进程或测试套件中运行。这可以通过在IDE中作为一个组执行所有测试来实现。类似地,当使用诸如Ant,Maven或Gradle之类的构建框架执行测试时,确保构建框架不在测试之间进行分配是很重要的。例如,如果Maven Surefire插件的forkMode设置为always或pertest,则TestContext框架将无法在测试类之间缓存应用程序上下文,因此构建过程将显着减慢运行速度。

从Spring Framework 4.3开始,上下文缓存的大小受限于默认的最大大小32.每当达到最大大小时,最近最少使用(LRU)驱逐策略用于驱逐和关闭过时的上下文。通过设置名为 spring.test.context.cache.maxSize 的JVM系统属性,可以从命令行或构建脚本配置最大大小。作为替代方案,可以通过 SpringProperties API以编程方式设置相同的属性。

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

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

上下文层次结构

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

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

以下基于JUnit 4的示例演示了需要使用上下文层次结构的集成测试的常见配置方案。

ControllerIntegrationTests 表示Spring MVC Web应用程序的典型集成测试场景,通过声明由两个级别组成的上下文层次结构,一个用于根WebApplicationContext(使用 TestAppConfig @Configuration 类加载),另一个用于调度程序servlet WebApplicationContext (使用 WebConfig @Configuration 类加载) )。自动装配到测试实例中的 WebApplicationContext 是子上下文的一个(即,层次结构中的最低上下文)。

@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 中的配置加载的应用程序上下文将被设置为为具体子类加载的每个上下文的父上下文。

@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 extends BaseTests 并指示Spring TestContext Framework合并 child 层次结构级别的上下文配置,只需确保通过 @ContextConfiguration 中的 name 属性声明的名称都是 "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"} 加载的上下文的父上下文。

@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" 加载的上下文。

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

如果在上下文配置为上下文层次结构的测试中使用@DirtiesContext,则hierarchyMode标志可用于控制如何清除上下文高速缓存。有关详细信息,请参阅讨论Spring Testing Annotations中的@DirtiesContext和@DirtiesContext javadocs。

15.5.5 测试夹具的依赖注入

当您使用默认配置的 DependencyInjectionTestExecutionListener 时,测试实例的依赖项将从您使用 @ContextConfiguration 配置的应用程序上下文中的bean中注入。您可以使用setter injection,field injection或两者,具体取决于您选择的注释以及是否将它们放在setter方法或字段上。为了与Spring 2.5和3.0中引入的注释支持保持一致,您可以使用Spring的 @Autowired 注释或JSR 330中的 @Inject 注释。

TestContext框架没有检测实例化测试实例的方式。因此,对于构造函数使用@Autowired或@Inject对测试类没有影响。

因为 @Autowired 用于执行 autowiring by type ,如果您有多个相同类型的bean定义,则不能对这些特定bean依赖此方法。在这种情况下,您可以将 @Autowired@Qualifier 结合使用。从Spring 3.0开始,您也可以选择将 @Inject@Named 结合使用。或者,如果您的测试类可以访问其 ApplicationContext ,则可以使用(例如)调用 applicationContext.getBean("titleRepository") 来执行显式查找。

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

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

以下代码清单中的依赖项注入行为并非特定于JUnit 4.相同的DI技术可与任何测试框架结合使用。以下示例调用静态断言方法,例如assertNotNull(),但不使用Assert预先调用。在这种情况下,假设通过示例中未显示的导入静态声明正确导入了该方法。

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

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

前面的代码清单使用 @ContextConfiguration 注释(即 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>

如果从Spring提供的测试基类扩展到在其某个setter方法上使用@Autowired,则可能在应用程序上下文中定义了多个受影响类型的bean:例如,多个DataSource bean。在这种情况下,您可以覆盖setter方法并使用@Qualifier批注指示特定的目标bean,如下所示,但也要确保委托给超类中的重写方法。 // ...

@Autowired
@覆盖
public void setDataSource(@Qualifier(“myDataSource”)DataSource dataSource){
super.setDataSource(数据源);
}

// ...指定的限定符值指示要注入的特定DataSource bean,将类型匹配集缩小到特定bean。它的值与相应定义中的声明匹配。 bean名称用作回退限定符值,因此您可以有效地按名称指向特定的bean(如上所示,假设“myDataSource”是bean id)。

15.5.6 测试请求和会话范围的bean

从早期开始,Spring就已经支持 Request and session scoped beans ,从Spring 3.2开始,按照这些步骤测试请求范围和会话范围的bean是一件轻而易举的事。

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

  • 将模拟请求或会话注入测试实例并根据需要准备测试夹具。

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

  • 对模拟执行断言。

以下代码段显示登录用例的XML配置。请注意, userService bean依赖于请求范围的 loginAction bean。此外,使用 SpEL expressions 实例化 LoginAction ,从当前HTTP请求中检索用户名和密码。在我们的测试中,我们希望通过TestContext框架管理的模拟配置这些请求参数。

Request-scoped bean configuration.

<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 (即我们刚刚设置参数的那个)。然后,我们可以根据用户名和密码的已知输入对结果执行断言。

Request-scoped bean test.

@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框架管理的模拟会话中配置主题。

Session-scoped bean configuration.

<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 ,并且我们可以根据配置的主题对结果执行断言。

Session-scoped bean test.

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

15.5.7 Transaction 管理

在TestContext框架中,事务由默认配置的 TransactionalTestExecutionListener 管理,即使您未在测试类上显式声明 @TestExecutionListeners 。但是,要启用对事务的支持,必须在 ApplicationContext 中配置 PlatformTransactionManager bean,该bean通过 @ContextConfiguration 语义加载(下面提供了更多详细信息)。此外,您必须在类或方法级别为测试声明Spring的 @Transactional 注释。

测试管理的事务

测试管理的事务是通过 TransactionalTestExecutionListener 以编程方式通过 TestTransaction (见下文)以声明方式管理的事务。此类事务不应与Spring管理的事务(即,在为测试加载的 ApplicationContext 中由Spring直接管理的事务)或应用程序管理的事务(即,通过测试调用的应用程序代码中以编程方式管理的事务)混淆。 Spring管理和应用程序管理的事务通常会参与测试管理的事务;但是,如果Spring管理的事务或应用程序管理的事务配置了除 REQUIRED 或_680376之外的任何传播类型,则应该小心(有关详细信息,请参阅 transaction propagation 上的讨论)。

启用和禁用事务

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

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

以下示例演示了为基于Hibernate的 UserRepository 编写集成测试的常见方案。如 the section called “Transaction rollback and commit behavior” 中所述,在执行 createUser() 方法后无需清理数据库,因为对数据库所做的任何更改都将由 TransactionalTestExecutionListener 自动回滚。有关其他示例,请参阅 Section 15.7, “PetClinic Example”

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

事务回滚和提交行为

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

程序化事务管理

从Spring Framework 4.1开始,可以通过 TestTransaction 中的静态方法以编程方式与测试管理的事务进行交互。例如, TestTransaction 可以在测试方法中使用,在方法之前,在方法之后用于启动或结束当前的测试管理事务,或者配置当前测试管理的事务以进行回滚或提交。只要启用 TransactionalTestExecutionListener ,就会自动提供对 TestTransaction 的支持。

以下示例演示了 TestTransaction 的一些功能。有关更多详细信息,请参阅 TestTransaction 的javadocs。

@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 注释。只需使用其中一个注释在测试类或测试接口中的任何 void 默认方法中注释任何 void 方法,并且 TransactionalTestExecutionListener 确保在适当的时间执行事务前方法或事务方法之后。

之前的任何方法(例如使用JUnit 4的@Before注释的方法)和任何后续方法(例如使用JUnit 4的@After注释的方法)都在事务中执行。此外,对于未配置为在事务中运行的测试方法,自然不会执行使用@BeforeTransaction或@AfterTransaction注释的方法。

配置事务管理器

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

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

以下基于JUnit 4的示例显示了一个虚构的集成测试场景,突出显示了所有与事务相关的注释。示例是 not 旨在演示最佳实践,而是演示如何使用这些注释。有关更多信息和配置示例,请参阅 annotation support 部分。 Transaction management for @Sql 包含一个使用 @Sql 的附加示例,用于使用默认事务回滚语义执行声明性SQL脚本。

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

}

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

@Autowired
SessionFactory sessionFactory;

@Transactional
@Test //没有预期的异常!
public void falsePositive(){
updateEntityInHibernateSession();
//误报:一旦Hibernate抛出异常
//会话最终被刷新(即在 生产环境 代码中)
}

@Transactional
@Test(期望= ...)
public void updateWithSessionFlush(){
updateEntityInHibernateSession();
//需要手动冲洗以避免测试中出现误报
sessionFactory.getCurrentSession()平齐();
}

// ...或JPA:// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test //没有预期的异常!
public void falsePositive(){
updateEntityInJpaPersistenceContext();
//误报:JPA会抛出异常
//最后刷新EntityManager(即在 生产环境 代码中)
}

@Transactional
@Test(期望= ...)
public void updateWithEntityManagerFlush(){
updateEntityInJpaPersistenceContext();
//需要手动冲洗以避免测试中出现误报
entityManager.flush();
}

// ...

15.5.8 执行SQL脚本

在针对关系数据库编写集成测试时,执行SQL脚本来修改数据库模式或将测试数据插入表中通常是有益的。 spring-jdbc 模块通过在加载Spring ApplicationContext 时执行SQL脚本来支持初始化嵌入或现有数据库。有关详细信息,请参阅 Section 19.8, “Embedded database support”Section 19.8.5, “Testing data access logic with an embedded database”

虽然在加载 ApplicationContext 时初始化数据库以进行一次测试非常有用,但有时在集成测试期间能够修改数据库是很重要的。以下部分说明如何在集成测试期间以编程方式和声明方式执行SQL脚本。

以编程方式执行SQL脚本

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

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

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

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

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

ScriptUtils 提供了一组用于处理SQL脚本的静态实用程序方法,主要用于框架内部使用。但是,如果您需要完全控制SQL脚本的解析和执行方式, ScriptUtils 可能比下面描述的其他一些替代方案更适合您的需求。有关详细信息,请参阅 ScriptUtils 中各个方法的javadocs。

ResourceDatabasePopulator 提供了一个简单的基于对象的API,用于使用外部资源中定义的SQL脚本以编程方式填充,初始化或清理数据库。 ResourceDatabasePopulator 提供选项配置解析和执行脚本时使用的字符编码,语句分隔符,注释分隔符和错误处理标志,并且每个配置选项都具有合理的默认值。有关默认值的详细信息,请参阅javadocs。要执行在 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脚本。有关更多详细信息,请参阅各种 executeSqlScript(..) 方法的javadocs。

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

除了上述以编程方式执行SQL脚本的机制之外,还可以在Spring TestContext Framework中以声明方式配置SQL脚本。具体来说,可以在测试类或测试方法上声明 @Sql 注释,以将资源路径配置为应在集成测试方法之前或之后针对给定数据库执行的SQL脚本。请注意,方法级声明会覆盖类级声明,并且 @Sql 支持 @Sql ,默认情况下启用。

Path resource semantics

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

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

@RunWith(SpringRunner.class)
@ContextConfiguration
@Sql("/test-schema.sql")
public class DatabaseTests {

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

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

Default script detection

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

  • 类级声明:如果带注释的测试类是 com.example.MyTest ,则相应的默认脚本是 "classpath:com/example/MyTest.sql"

  • 方法级声明:如果带注释的测试方法名为 testMethod() 并且在类 com.example.MyTest 中定义,则相应的默认脚本为 "classpath:com/example/MyTest.testMethod.sql"

    Declaring multiple @Sql sets

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

以下示例演示如何使用Java 8将 @Sql 用作可重复的注释。在此方案中, test-schema.sql 脚本对单行注释使用不同的语法。

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

以下示例与上述示例相同,只是 @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
}

Script execution phases

默认情况下,SQL脚本将在相应的测试方法之前执行。但是,如果需要在测试方法之后执行特定的脚本集 - 例如,要清理数据库状态 - 可以使用 @Sql 中的 executionPhase 属性,如以下示例所示。请注意, ISOLATEDAFTER_TEST_METHOD 分别从 Sql.TransactionModeSql.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
}

Script configuration with @SqlConfig

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

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

Transaction management for @Sql

默认情况下, SqlScriptsTestExecutionListener 将推断通过 @Sql 配置的脚本所需的事务语义。具体来说,SQL脚本将在没有事务的情况下执行,在现有的Spring管理的事务中 - 例如,由 TransactionalTestExecutionListener 管理的事务用于使用 @Transactional 注释的测试 - 或者在隔离的事务中,具体取决于 transactionMode 属性的配置值在 @SqlConfig 中,在测试的 ApplicationContext 中存在 PlatformTransactionManager 。然而,作为最低限度,测试的 ApplicationContext 中必须存在 javax.sql.DataSource

如果 SqlScriptsTestExecutionListener 使用的算法检测 DataSourcePlatformTransactionManager 并推断事务语义不符合您的需要,您可以通过 @SqlConfigdataSourcetransactionManager 属性指定显式名称。此外,事务传播行为可以通过 @SqlConfigtransactionMode 属性进行控制 - 例如,如果脚本应该在隔离的事务中执行。尽管使用 @Sql 对事务管理的所有受支持选项的详尽讨论超出了本参考手册的范围,但 @SqlConfigSqlScriptsTestExecutionListener 的javadoc提供了详细信息,以下示例演示了使用JUnit 4和使用 @Sql 的事务测试的典型测试场景。请注意,执行 usersTest() 方法后无需清理数据库,因为对数据库所做的任何更改(在测试方法内或在 /test-data.sql 脚本中)都将由 TransactionalTestExecutionListener 自动回滚(有关详细信息,请参阅 transaction management ) 。

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestDatabaseConfig.class)
@Transactional
public class TransactionalSqlScriptsTests {

    protected JdbcTemplate jdbcTemplate;

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

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

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

15.5.9 TestContext框架支持类

Spring JUnit 4 Runner

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

以下代码清单显示了配置测试类以使用自定义Spring Runner 运行的最低要求。 @TestExecutionListeners 配置了一个空列表以禁用默认侦听器,否则需要通过 @ContextConfiguration 配置 ApplicationContext

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

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

Spring JUnit 4规则

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

  • SpringClassRule

  • SpringMethodRule

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

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

  • AbstractTransactionalJUnit4SpringContextTests

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

AbstractTransactionalJUnit4SpringContextTestsAbstractJUnit4SpringContextTests 的抽象事务扩展,为JDBC访问添加了一些便利功能。这个类需要一个 javax.sql.DataSource bean和一个 PlatformTransactionManager bean在 ApplicationContext 中定义。扩展 AbstractTransactionalJUnit4SpringContextTests 时,可以访问可用于执行SQL语句查询数据库的 protected jdbcTemplate 实例变量。此类查询可用于在执行与数据库相关的应用程序代码之前和之后确认数据库状态,Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具结合使用时,请务必避免使用 false positives 。如 Section 15.3, “JDBC Testing Support” 中所述, AbstractTransactionalJUnit4SpringContextTests 还提供了方便的方法,使用前面提到的 jdbcTemplate 委托 JdbcTestUtils 中的方法。此外, AbstractTransactionalJUnit4SpringContextTests 提供了 executeSqlScript(..) 方法,用于针对配置的 DataSource 执行SQL脚本。

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

JUnit 5支持

Spring Framework 5.0提供了与JUnit 5中引入的JUnit Jupiter测试框架的完全集成。因此,鼓励开发人员升级到Spring 5.x以充分受益于Spring对JUnit 5的支持。但是,如果您的项目由于某种原因还无法升级到Spring 5.x,您可能有兴趣使用 spring-test-junit5 项目作为临时解决方案来帮助您在使用Spring Framework 4.3.x时升级到JUnit 5。

TestNG支持类

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

  • AbstractTestNGSpringContextTests

  • AbstractTransactionalTestNGSpringContextTests

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

AbstractTransactionalTestNGSpringContextTestsAbstractTestNGSpringContextTests 的抽象事务扩展,为JDBC访问添加了一些便利功能。该类需要在 ApplicationContext 中定义 javax.sql.DataSource bean和 PlatformTransactionManager bean。扩展 AbstractTransactionalTestNGSpringContextTests 时,可以访问可用于执行SQL语句查询数据库的 protected jdbcTemplate 实例变量。此类查询可用于在执行与数据库相关的应用程序代码之前和之后确认数据库状态,Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具结合使用时,请务必避免使用 false positives 。如 Section 15.3, “JDBC Testing Support” 中所述, AbstractTransactionalTestNGSpringContextTests 还提供了方便的方法,使用前面提到的 jdbcTemplate 委托 JdbcTestUtils 中的方法。此外, AbstractTransactionalTestNGSpringContextTests 提供了 executeSqlScript(..) 方法,用于针对配置的 DataSource 执行SQL脚本。

这些类是扩展的便利。如果你不希望你的测试类绑定到Spring的类上,则可以通过@ContextConfiguration,@TestExecutionListeners配置你自己的测试类,等等,并通过使用TestContextManager手动插装您的测试类。有关如何检测测试类的示例,请参阅AbstractTestNGSpringContextTests的源代码。

15.6 Spring MVC测试框架

Spring MVC Test框架提供了一流的支持,可以使用可以与JUnit,TestNG或任何其他测试框架一起使用的流畅API来测试Spring MVC代码。它 Build 在 spring-test 模块的 Servlet API mock objects 上,因此不使用正在运行的Servlet容器。它使用 DispatcherServlet 提供完整的Spring MVC运行时行为,并提供对使用TestContext框架加载实际Spring配置的支持,以及独立模式,其中控制器可以手动实例化并一次测试一个。

Spring MVC Test还为测试使用 RestTemplate 的代码提供客户端支持。客户端测试模拟服务器响应,也不使用正在运行的服务器。

Spring Boot提供了一个选项来编写包含正在运行的服务器的完整的端到端集成测试。如果这是您的目标,请查看Spring Boot参考页面。有关容器外和端到端集成测试之间差异的更多信息,请参阅“容器外和端到端集成测试之间的差异”一节。

15.6.1 服务器端测试

可以很容易地编写使用JUnit或TestNG的一个Spring MVC的控制器,一个普通的单元测试:只要实现控制器,以嘲笑或存根依赖注入它,并调用它的方法传递 MockHttpServletRequestMockHttpServletResponse 等,是必要的。但是,在编写这样的单元测试时,仍有许多未经测试:例如,请求映射,数据绑定,类型转换,验证等等。此外,其他控制器方法,如 @InitBinder@ModelAttribute@ExceptionHandler 也可以作为请求处理生命周期的一部分调用。

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

Spring MVC Test Build 在 spring-test 模块中熟悉的 "mock" implementations of the Servlet API 之上。这允许执行请求并生成响应,而无需在Servlet容器中运行。在大多数情况下,一切都应该像在运行时那样工作,但有一些值得注意的例外,如 the section called “Differences between Out-of-Container and End-to-End Integration Tests” 中所述。这是一个使用Spring MVC Test的基于JUnit 4的示例:

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

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

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

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

    @Test
    public 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,内容类型为 "application/json" ,响应正文具有名为 "name" 的JSON属性,值为 "Lee" 。 Jayway JsonPath project 支持 jsonPath 语法。还有许多其他选项可用于验证将在下面讨论的已执行请求的结果。

静态进口

上例中的流畅API需要一些静态导入,例如 MockMvcRequestBuilders.*MockMvcResultMatchers.*MockMvcBuilders.* 。查找这些类的简单方法是搜索匹配 "MockMvc*" 的类型。如果使用Eclipse,请确保在Java→编辑器→内容辅助→收藏夹下的Eclipse首选项中将它们添加为 "favorite static members" 。这将允许在键入静态方法名称的第一个字符后使用内容辅助。其他IDE(例如IntelliJ)可能不需要任何其他配置。只需检查静态成员的代码完成支持。

设置选择

创建 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配置将模拟服务注入控制器,以便继续专注于测试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" 也是编写临时测试以验证特定行为或调试问题的一种非常方便的方法。

就像任何“整合与单元测试”辩论一样,没有正确或错误的答案。但是,使用“standaloneSetup”确实意味着需要额外的“webAppContextSetup”测试才能验证Spring MVC配置。或者,您可以选择使用“webAppContextSetup”编写所有测试,以便始终针对您的实际Spring MVC配置进行测试。

执行请求

使用任何HTTP方法执行请求都很容易:

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

您还可以执行内部使用 MockMultipartHttpServletRequest 的文件上载请求,这样就不会对多部分请求进行实际解析,而是必须进行设置:

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

您可以在URI模板样式中指定查询参数:

mockMvc.perform(get("/hotels?foo={foo}", "bar"));

或者您可以添加表示表单参数查询的Servlet请求参数:

mockMvc.perform(get("/hotels").param("foo", "bar"));

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

在大多数情况下,最好从请求URI中省略上下文路径和Servlet路径。如果必须使用完整请求URI进行测试,请务必设置 contextPathservletPath 因此,请求映射将起作用:

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

查看上面的示例,使用每个执行的请求设置contextPath和servletPath是很麻烦的。相反,您可以设置默认请求属性:

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无关紧要的原因,因为必须在每个请求中指定它们。

定义期望

可以通过在执行请求后附加一个或多个 .andExpect(..) 调用来定义期望:

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

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

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

第二类断言超出了回应范围。这些断言允许人们检查Spring MVC的特定方面,例如处理请求的控制器方法,是否引发和处理异常,模型的内容是什么,选择了哪个视图,添加了哪些flash属性等等。它们还允许用户检查Servlet特定方面,例如请求和会话属性。

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

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

过滤器注册

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

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

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

容器外和端到端集成测试之间的差异

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

最简单的思考方法是从空白 MockHttpServletRequest 开始。无论你添加什么,都是请求的内容。令您惊讶的是,默认情况下没有上下文路径,没有 jsessionid cookie,没有转发,错误或异步调度,因此没有实际的JSP呈现。相反, "forwarded" 和 "redirected" URL保存在 MockHttpServletResponse 中,并且可以满足期望。

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

或者,您可以考虑通过 @WebIntegrationTest 从Spring Boot获得完整的端到端集成测试支持。见 Spring Boot reference

每种方法都有利弊。 Spring MVC Test中提供的选项在从经典单元测试到完全集成测试的规模上是不同的停止。可以肯定的是,Spring MVC Test中没有任何选项可供选择在经典单元测试的范畴下,但它们更接近它。例如,您可以通过将模拟服务注入控制器来隔离Web层,在这种情况下,您只通过 DispatcherServlet 测试Web层,但使用实际的Spring配置,就像您可以独立于层测试数据访问层一样以上。或者,您可以一次使用专注于一个控制器的独立设置,并手动提供使其工作所需的配置。

使用Spring MVC Test时的另一个重要区别是概念上这些测试位于服务器端内部,因此您可以检查使用了什么处理程序,是否使用HandlerExceptionResolver处理异常,模型的内容是什么,绑定是什么这意味着更容易编写期望,因为服务器不是黑盒子,因为它是通过实际的HTTP客户端测试它。这通常是经典单元测试的一个优点,它更容易编写,推理和调试,但不能取代完全集成测试的需要。与此同时,重要的是不要忽视响应是最重要的检查事实。简而言之,即使在同一个项目中,也存在多种样式和测试策略的空间。

进一步的服务器端测试示例

框架自己的测试包括 many sample tests ,旨在演示如何使用Spring MVC Test。浏览这些示例以获取更多想法。此外, spring-mvc-showcase 具有基于Spring MVC Test的完整测试覆盖率。

15.6.2 HtmlUnit集成

Spring提供 MockMvcHtmlUnit 之间的集成。这简化了在使用基于HTML的视图时执行端到端测试的过程。此集成使开发人员能够:

  • 使用 HtmlUnitWebDriver 和_680726等工具轻松测试HTML页面,无需部署到Servlet容器

  • 在页面中测试JavaScript

  • (可选)使用模拟服务进行测试以加快测试速度

  • 在容器内端到端测试和容器外集成测试之间共享逻辑

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

为何选择HtmlUnit?

想到的最明显的问题是, "Why do I need this?" 。通过探索一个非常基本的示例应用程序可以找到答案。假设您有一个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表单与控制器不同步,我们的表单测试仍会继续通过。要解决这个问题,我们可以结合两个测试。

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

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

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

这样可以降低我们的测试错误传递的风险,但仍然存在一些问题。

  • 如果我们的页面上有多个表单怎么办?不可否认,我们可以更新我们的xpath表达式,但是我们考虑的因素越多,它们就越复杂(字段的类型是否正确?字段是否已启用?等等)。

  • 另一个问题是我们正在做的工作量是我们预期的两倍。我们必须首先验证视图,然后我们使用我们刚刚验证的相同参数提交视图。理想情况下,这可以一次完成。

  • 最后,还有一些我们仍然无法解释的事情。例如,如果表单具有我们希望测试的JavaScript验证,该怎么办?

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

整合测试救援?

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

  • 我们的页面是否向用户显示通知,表明当消息为空时没有结果可用?

  • 我们的页面是否正确显示单个消息?

  • 我们的页面是否正确支持分页?

要设置这些测试,我们需要确保我们的数据库中包含正确的消息。这导致了许多额外的挑战。

  • 确保数据库中存在正确的消息可能很乏味;考虑外键约束。

  • 测试可能会变慢,因为每个测试都需要确保数据库处于正确的状态。

  • 由于我们的数据库需要处于特定状态,因此我们无法并行运行测试。

  • 对自动生成的ID,时间戳等事物执行断言可能很困难。

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

输入HtmlUnit Integration

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

HtmlUnit集成选项

有许多方法可以将 MockMvc 与HtmlUnit集成。

  • MockMvc and HtmlUnit :如果要使用原始HtmlUnit库,请使用此选项。

  • MockMvc and WebDriver :使用此选项可以简化集成和端到端测试之间的开发和重用代码。

  • MockMvc and Geb :如果您希望在集成和端到端测试之间使用Groovy进行测试,简化开发和重用代码,请使用此选项。

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

这是使用MockMvcWebClientBuilder的一个简单示例。有关高级用法,请参阅“Advanced MockMvcWebClientBuilder”一节

这将确保任何引用 localhost 作为服务器的URL将被定向到我们的 MockMvc 实例,而无需真正的HTTP连接。正常情况下,将使用网络连接请求任何其他URL。这使我们可以轻松测试CDN的使用。

MockMvc和HtmlUnit用法

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

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

默认的上下文路径是“”。或者,我们可以指定上下文路径,如“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();

最后,我们可以验证是否已成功创建新消息。以下断言使用 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 uses the Mozilla Rhino engine 来评估JavaScript。这意味着我们也可以在页面中测试JavaScript的行为!

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

高级MockMvcWebClientBuilder

在到目前为止的示例中,我们以最简单的方式使用了 MockMvcWebClientBuilder ,通过构建 WebClient ,基于Spring TestContext Framework为我们加载的 WebApplicationContext 。这里重复这种方法。

@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 的全部功能。

有关创建MockMvc实例的其他信息,请参阅“安装选择”一节。

MockMvc和WebDriver

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

为什么选择WebDriver和MockMvc?

我们已经可以使用HtmlUnit和 MockMvc 了,为什么我们要使用 WebDriver ? Selenium WebDriver 提供了一个非常优雅的API,使我们可以轻松地组织我们的代码。为了更好地理解,让我们来探索一下例。

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

假设我们需要确保正确创建消息。测试涉及查找HTML表单输入元素,填写它们以及进行各种断言。

这种方法导致大量单独的测试,因为我们也想测试错误条件。例如,如果我们只填写表单的一部分,我们希望确保收到错误。如果我们填写整个表格,则应在之后显示新创建的消息。

如果其中一个字段被命名为“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时不必更新所有测试。

我们甚至可以更进一步,将此逻辑放在一个Object中,该Object代表我们当前所在的_680776。

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

以前,这种模式被称为 Page Object Pattern 。虽然我们当然可以使用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();
}

这是使用MockMvcHtmlUnitDriverBuilder的一个简单示例。有关更高级的用法,请参阅“Advanced MockMvcHtmlUnitDriverBuilder”一节

这将确保任何引用 localhost 作为服务器的URL将被定向到我们的 MockMvc 实例,而无需真正的HTTP连接。正常情况下,将使用网络连接请求任何其他URL。这使我们可以轻松测试CDN的使用。

MockMvc和WebDriver用法

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

CreateMessagePage page = CreateMessagePage.to(driver);

然后我们可以填写表单并提交它以创建消息。

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

这通过利用页面对象模式改进了 HtmlUnit test 的设计。正如我们在 the section called “Why WebDriver and MockMvc?” 中提到的,我们可以将页面对象模式与HtmlUnit一起使用,但使用WebDriver会更容易。让我们来看看我们新的 CreateMessagePage 实现。

public class CreateMessagePage
        extends AbstractPage { 

    
    private WebElement summary;
    private WebElement text;

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

你会注意到的第一件事是 CreateMessagePage 扩展了 AbstractPage 。我们不会详细介绍 AbstractPage 的详细信息,但总的来说它包含了我们所有页面的常用功能。例如,如果我们的应用程序具有导航栏,全局错误消息等,则可以将此逻辑放置在共享位置。

您将注意到的下一件事是我们感兴趣的HTML页面的每个部分都有一个成员变量。这些部分的类型为 WebElementWebDriverPageFactory 允许我们通过自动解析每个 WebElement 从HtmlUnit版本的 CreateMessagePage 中删除大量代码。 PageFactory#initElements(WebDriver,Class) 方法将使用字段名称自动解析每个 WebElement ,并通过HTML页面中元素的 idname 查找它。

我们可以使用 @FindBy annotation 来覆盖默认的查找行为。我们的示例演示了如何使用 @FindBy 注释使用css选择器 input[type=submit] 查找提交按钮。

最后,我们可以验证是否已成功创建新消息。以下断言使用 FEST assertion library

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

高级MockMvcHtmlUnitDriverBuilder

在到目前为止的示例中,我们以最简单的方式使用 MockMvcHtmlUnitDriverBuilder ,通过构建基于Spring TestContext Framework为我们加载的 WebApplicationContextWebDriver 。这里重复这种方法。

@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 的全部功能。

有关创建MockMvc实例的其他信息,请参阅“调用”一节“设置选择”。

MockMvc和Geb

在上一节中,我们了解了如何将 MockMvcWebDriver 一起使用。在本节中,我们将使用 Geb 来使我们的测试甚至是Groovy-er。

为什么选择Geb和MockMvc?

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

MockMvc和Geb设置

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

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

这是使用MockMvcHtmlUnitDriverBuilder的一个简单示例。有关更高级的用法,请参阅“Advanced MockMvcHtmlUnitDriverBuilder”一节

这将确保任何引用 localhost 作为服务器的URL将被定向到我们的 MockMvc 实例,而无需真正的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 。这就是为什么我们可以断言我们在正确的页面上如下。

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

我们在闭包中使用断言,这样如果我们在错误的页面,我们就可以确定出错的地方。

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

最后,我们可以验证是否已成功创建新消息。

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

有关如何充分利用Geb的更多详细信息,请参阅 The Book of Geb 用户手册。

15.6.3 客户端REST测试

客户端测试可用于测试内部使用 RestTemplate 的代码。我们的想法是声明预期的请求并提供 "stub" 响应,以便您可以专注于单独测试代码,即无需运行服务器。这是一个例子:

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 (客户端REST测试的中心类)使用自定义 ClientHttpRequestFactory 配置 RestTemplate ,该自定义 ClientHttpRequestFactory 根据预期断言实际请求并返回 "stub" 响应。在这种情况下,我们期望 "/greeting" 的请求,并希望返回带有 "text/plain" 内容的200响应。我们可以根据需要定义额外的预期请求和存根响应。当定义了预期的请求和存根响应时, RestTemplate 可以像往常一样在客户端代码中使用。在测试结束时 mockServer.verify() 可用于验证是否已满足所有期望。

默认情况下,请求按预期声明的顺序排列。您可以在构建服务器时设置 ignoreExpectOrder 选项,在这种情况下,检查所有期望(按顺序)以查找给定请求的匹配项。这意味着允许请求以任何顺序出现。这是一个例子:

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

即使是默认的无序请求,每个请求也只允许执行一次。 expect 方法提供了一个重载变量,它接受一个指定计数范围的 ExpectedCount 参数,例如: oncemanyTimesmaxminbetween 等。这是一个例子:

RestTemplate restTemplate = new RestTemplate();

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

// ...

mockServer.verify();

请注意,如果未设置 ignoreExpectOrder (默认值),因此请求按声明顺序排列,则该顺序仅适用于任何预期请求中的第一个。例如,如果 "/foo" 预期2次,然后是 "/bar" 3次,则在向 "/bar" 发出请求之前应该有 "/foo" 的请求,但除此之外可以随时发出 "/foo" 和 "/bar" 请求。

作为上述所有选项的替代方案,客户端测试支持还提供了 ClientHttpRequestFactory 实现,可以将其配置为 RestTemplate 以将其绑定到 MockMvc 实例。这允许使用实际的服务器端逻辑处理请求但不运行服务器。这是一个例子:

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

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

mockServer.verify();

静态进口

就像服务器端测试一样,用于客户端测试的流畅API需要一些静态导入。通过搜索 "MockRest*" 很容易找到。 Eclipse用户应在Java→编辑器→内容辅助→收藏夹下的Eclipse首选项中添加 "MockRestRequestMatchers.*""MockRestResponseCreators.*" 作为 "favorite static members" 。这允许在键入静态方法名称的第一个字符后使用内容辅助。其他IDE(例如IntelliJ)可能不需要任何其他配置。只需检查静态成员的代码完成支持。

客户端REST测试的更多示例

Spring MVC Test自己的测试包括 example tests 客户端REST测试。

15.7 PetClinic示例

PetClinic应用程序在 GitHub 上提供,说明了JUnit 4环境中Spring TestContext Framework的几个特性。大多数测试功能都包含在 AbstractClinicTests 中,部分列表如下所示:

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

@ContextConfiguration
public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    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());
        Vet v1 = EntityUtils.getById(vets, Vet.class, 2);
        assertEquals("Leary", v1.getLastName());
        assertEquals(1, v1.getNrOfSpecialties());
        assertEquals("radiology", (v1.getSpecialties().get(0)).getName());
        // ...
    }

    // ...
}

笔记:

  • 此测试用例扩展了 AbstractTransactionalJUnit4SpringContextTests 类,从该类继承了依赖注入(通过 DependencyInjectionTestExecutionListener )和事务行为(通过 TransactionalTestExecutionListener )的配置。

  • clinic 实例变量 - 正在测试的应用程序对象 - 由依赖注入通过 @Autowired 语义设置。

  • getVets() 方法说明了如何使用继承的 countRowsInTable() 方法轻松验证给定表中的行数,从而验证正在测试的应用程序代码的正确行为。这允许更强的测试并减少对精确测试数据的依赖性。例如,您可以在数据库中添加其他行而不会破坏测试。

  • 与许多使用数据库的集成测试一样, AbstractClinicTests 中的大多数测试依赖于测试用例运行之前数据库中已有的最小数据量。或者,您可以选择在测试用例的测试夹具设置中填充数据库 - 再次,在与测试相同的事务中。

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

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

@ContextConfiguration
public class HibernateClinicTests extends AbstractClinicTests { }

在大型应用程序中,Spring配置通常分为多个文件。因此,配置位置通常在所有特定于应用程序的集成测试的公共基类中指定。这样的基类也可以添加有用的实例变量 - 自然地由依赖注入填充 - 例如在使用Hibernate的应用程序的情况下 SessionFactory

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

Updated at: 5 months ago
14.2.2. Spring MVCTable of content16. 更多资源
Comment
You are not logged in.

There are no comments.