Spring TestExecutionListener

2023/05/10

1. 概述

通常,我们使用@BeforeEach、@AfterEach、@BeforeAll和@AfterAll等JUnit注解来编排测试的生命周期,但有时这还不够-尤其是在我们使用Spring框架时。

这就是Spring TestExecutionListener派上用场的地方。

在本教程中,我们将了解TestExecutionListener提供的作用、Spring提供的默认监听器以及如何实现自定义TestExecutionListener

2. TestExecutionListener接口

首先,下面是TestExecutionListener接口的简单定义:

public interface TestExecutionListener {
    default void beforeTestClass(TestContext testContext) throws Exception {
    }

    default void prepareTestInstance(TestContext testContext) throws Exception {
    }

    default void beforeTestMethod(TestContext testContext) throws Exception {
    }

    default void afterTestMethod(TestContext testContext) throws Exception {
    }

    default void afterTestClass(TestContext testContext) throws Exception {
    }
}

该接口的实现可以在不同的测试执行阶段接收事件。因此,接口中的每个方法都接收一个TestContext对象作为参数。

此TestContext对象包含Spring上下文以及目标测试类和方法的信息。此信息可用于更改测试的行为或扩展其功能。

现在,让我们快速浏览一下这些方法:

  • afterTestClass:在类中的所有测试都执行后的后置处理
  • afterTestExecution:在提供的测试上下文中执行测试方法后立即执行的后置处理
  • afterTestMethod:在执行底层测试框架的生命周期after回调后的后置处理
  • beforeTestClass:在类中的所有测试都执行前的前置处理
  • beforeTestExecution:在提供的测试上下文中执行测试方法前立即执行的前置处理
  • beforeTestMethod:在执行底层测试框架的生命周期before回调前的前置处理
  • prepareTestInstance:准备所提供测试上下文的测试实例

值得注意的是,此接口为所有方法提供了空的默认实现。因此,具体的实现可以选择只覆盖那些自己需要的方法。

3. Spring默认的TestExecutionListeners

默认情况下,Spring提供了一些开箱即用的TestExecutionListener实现。

让我们快速看一下其中的每一个:

  • ServletTestExecutionListener:为WebApplicationContext配置Servlet API Mock
  • DirtiesContextBeforeModesTestExecutionListener:处理“before”模式的@DirtiesContext注解
  • DependencyInjectionTestExecutionListener:为测试实例提供依赖注入
  • DirtiesContextTestExecutionListener:处理“after”模式的@DirtiesContext注解
  • TransactionalTestExecutionListener:提供具有默认回滚语义的事务测试执行
  • SqlScriptsTestExecutionListener:运行使用@Sql注解配置的SQL脚本

这些监听器完全按照列出的顺序预先注册。当我们创建一个自定义的TestExecutionListener时,我们将看到更多关于顺序的信息。

4. 使用自定义TestExecutionListener

现在,让我们定义一个自己的TestExecutionListener实现:

public class CustomTestExecutionListener implements TestExecutionListener, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(CustomTestExecutionListener.class);

    public void beforeTestClass(TestContext testContext) {
        logger.info("beforeTestClass : {}", testContext.getTestClass());
    }

    public void prepareTestInstance(TestContext testContext) {
        logger.info("prepareTestInstance : {}", testContext.getTestClass());
    }

    public void beforeTestMethod(TestContext testContext) {
        logger.info("beforeTestMethod : {}", testContext.getTestMethod());
    }

    public void afterTestMethod(TestContext testContext) {
        logger.info("afterTestMethod : {}", testContext.getTestMethod());
    }

    public void afterTestClass(TestContext testContext) {
        logger.info("afterTestClass : {}", testContext.getTestClass());
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }
}

为简单起见,这个类所做的只是记录一些TestContext信息。

4.1 使用@TestExecutionListeners注册自定义监听器

现在,让我们在测试类中使用这个监听器。为此,我们需要使用@TestExecutionListeners注解注册它:

@ExtendWith(SpringExtension.class)
@TestExecutionListeners(value = {CustomTestExecutionListener.class, DependencyInjectionTestExecutionListener.class})
@ContextConfiguration(classes = AdditionService.class)
class TestExecutionListenersWithoutMergeModeUnitTest {
    // ...
}

重要的是要注意,使用@TestExecutionListeners注解会取消注册所有默认的监听器。因此,我们显式添加了DependencyInjectionTestExecutionListener,以便我们可以在测试类中使用自动注入。

如果我们需要任何其他默认监听器,则必须指定它们中的每一个。但是,我们也可以使用该注解的mergeMode属性:

@TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)

这里,MERGE_WITH_DEFAULTS表示自己声明的监听器应该与默认监听器合并。

现在,当我们运行上述测试时,监听器将记录它接收到的每个事件:

15:33:19.186 [main] INFO org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener@e350b40, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@41a0aa7d]
15:33:19.187 [main] INFO cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener - beforeTestClass : class cn.tuyucheng.taketoday.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
15:33:19.199 [main] INFO cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener - prepareTestInstance : class cn.tuyucheng.taketoday.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
...
15:33:19.407 [main] INFO cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener - beforeTestMethod : void cn.tuyucheng.taketoday.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest.whenValidNumbersPassed_thenReturnSum()
15:33:19.434 [main] INFO cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener - afterTestMethod : void cn.tuyucheng.taketoday.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest.whenValidNumbersPassed_thenReturnSum()
15:33:19.445 [main] INFO cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener - afterTestClass : class cn.tuyucheng.taketoday.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest

4.2 自动扫描默认TestExecutionListener实现

如果只在有限数量的测试类中使用,那么使用@TestExecutionListener注册监听器是合适的。但是,将它添加到包含大量测试类的测试套件中可能会变得很麻烦。

我们可以通过利用SpringFactoriesLoader机制提供的自动发现TestExecutionListener实现的支持来解决这个问题。

spring-test模块在其META-INF/spring.factories属性文件中的org.springframework.test.context.TestExecutionListener键下声明所有核心默认监听器。类似地,我们可以通过在我们自己的META-INF/spring.factories属性文件中使用上述键来注册我们的自定义监听器

org.springframework.test.context.TestExecutionListener=cn.tuyucheng.taketoday.testexecutionlisteners.CustomTestExecutionListener

4.3 排序默认的TestExecutionListener实现

当Spring通过SpringFactoriesLoader机制扫描默认的TestExecutionListener实现时,它将使用Spring的AnnotationAwareOrderComparator对它们进行排序。这遵循Spring的Ordered接口和用于排序的@Order注解。

请注意,Spring提供的所有默认TestExecutionListener实现都使用适当的值实现了Ordered接口的getOrder方法。因此,我们必须确保我们的自定义TestExecutionListener实现以正确的顺序注册。所以,我们需要在自定义监听器中同样实现Ordered接口:

public class CustomTestExecutionListener implements TestExecutionListener, Ordered {

    // ...

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }
}

或者我们也可以在自定义监听器类上使用@Order注解来代替。

5. 总结

在本文中,我们了解了如何实现自定义TestExecutionListener。我们还介绍了Spring框架提供的一些默认监听器。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章