1. 概述

本文将深入讲解 Spring 测试框架中的 @DirtiesContext 注解,帮助你在集成测试中更好地管理 Spring 应用上下文(ApplicationContext)的生命周期。✅

这个注解常被用在那些会“污染”应用上下文的测试中,比如修改了单例 Bean 的状态。如果不做处理,这些变更会影响后续测试,导致测试之间相互依赖 —— 这是集成测试的大忌 ❌。

我们还会通过实际代码示例,展示如何正确使用该注解来隔离测试,避免“前一个测试的脏数据影响下一个测试”这类经典踩坑问题。

2. @DirtiesContext 是什么

@DirtiesContext 是 Spring Test 框架提供的一个测试注解,用于标记某个测试类或测试方法会修改 Spring 的 ApplicationContext

一旦标记,Spring 测试框架会在指定时机将当前上下文标记为“已污染”(dirty),并触发其关闭和重建。⚠️

你可以将它用在:

  • 类级别:作用于整个测试类
  • 方法级别:仅作用于某个测试方法

通过配置 ClassModeMethodMode,你可以精确控制上下文重建的时机。

✅ 小贴士:如果加在类上,所有测试方法都会遵循设定的 ClassMode 行为。

3. 不清理上下文的测试问题

我们先看一个典型的“测试污染”场景。

假设有一个简单的 User 类:

public class User {
    String firstName;
    String lastName;
}

再定义一个带缓存功能的 UserCache 组件:

@Component
public class UserCache {

    @Getter
    private Set<String> userList = new HashSet<>();

    public boolean addUser(String user) {
        return userList.add(user);
    }

    public void printUserList(String message) {
        System.out.println(message + ": " + userList);
    }
}

接着写一个集成测试类:

@TestMethodOrder(OrderAnnotation.class)
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringDataRestApplication.class)
class DirtiesContextIntegrationTest {

    @Autowired
    protected UserCache userCache;
    
    ...
}

第一个测试:添加用户

@Test
@Order(1)
void addJaneDoeAndPrintCache() {
    userCache.addUser("Jane Doe");
    userCache.printUserList("addJaneDoeAndPrintCache");
}

输出:

addJaneDoeAndPrintCache: [Jane Doe]

第二个测试:再次读取

@Test
@Order(2)
void printCache() {
    userCache.printUserList("printCache");
}

输出:

printCache: [Jane Doe]

看到没?缓存里的数据还在!因为 UserCache 是单例 Bean,上下文没重启,它的状态就被保留了。

⚠️ 问题来了:如果第三个测试期望缓存是空的,那就会因为“残留数据”导致断言失败 —— 这就是典型的测试污染。

4. 使用 @DirtiesContext 解决问题

现在我们引入 @DirtiesContext,让 Spring 在特定测试后重建上下文。

默认行为是 methodMode = AFTER_METHOD,即:当前测试方法执行完后,标记上下文为脏并重建

来看具体用法:

@DirtiesContext(methodMode = MethodMode.AFTER_METHOD)
@Test
@Order(3)
void addJohnDoeAndPrintCache() {
    userCache.addUser("John Doe");
    userCache.printUserList("addJohnDoeAndPrintCache");
}

输出:

addJohnDoeAndPrintCache: [John Doe, Jane Doe]

💡 注意:此时缓存中仍有之前添加的 "Jane Doe",因为 @DirtiesContext 是在方法结束后才触发重建。

接下来运行第四个测试:

@Test
@Order(4)
void printCacheAgain() {
    userCache.printUserList("printCacheAgain");
}

输出:

printCacheAgain: []

✅ 成功了!因为在 addJohnDoeAndPrintCache 执行完后,Spring 重建了上下文,UserCache 被重新初始化,缓存清空。

5. 支持的上下文重置时机

@DirtiesContext 支持多种触发时机,分为类级别和方法级别。

5.1 类级别(ClassMode)

适用于整个测试类,可选值如下:

  • BEFORE_CLASS:在当前测试类执行前重建上下文
  • BEFORE_EACH_TEST_METHOD:每个测试方法前都重建
  • AFTER_EACH_TEST_METHOD:每个测试方法后都重建
  • AFTER_CLASS:在当前测试类执行完毕后重建(默认)

✅ 适用场景:如果你的整个测试类都会修改上下文状态,推荐使用 AFTER_EACH_TEST_METHOD 来彻底隔离。

5.2 方法级别(MethodMode)

仅作用于单个测试方法:

  • BEFORE_METHOD:在当前方法执行前重建上下文
  • AFTER_METHOD:在当前方法执行后重建上下文(默认)

✅ 推荐用法:大多数情况下使用 AFTER_METHOD 就够了,简单粗暴有效。

6. 总结

@DirtiesContext 是解决 Spring 集成测试中上下文污染问题的利器。⚠️

关键点回顾:

  • 当测试修改了单例 Bean 的状态时,务必考虑使用 @DirtiesContext
  • 方法级别用 AFTER_METHOD,类级别根据需要选择 AFTER_EACH_TEST_METHOD
  • 上下文重建有性能开销,不要滥用 ❌,只在必要时使用

示例代码已上传至 GitHub:https://github.com/yourname/spring-testing-examples


原始标题:A Quick Guide to @DirtiesContext