Spring 测试:TestContext 与事务回滚
引言
单元测试要 Mock,集成测试要真容器。Spring 的 TestContext 框架让你在 JUnit 里启动一个真实 ApplicationContext,把 Bean 注入测试类;更妙的是测试方法标 @Transactional 时默认回滚,写库测试互不污染。本章掌握集成测试的最小骨架与回滚语义。
核心概念
- 模块:
spring-test;JUnit 5 用@ExtendWith(SpringExtension.class)接入,或直接用组合注解@SpringJUnitConfig(= ExtendWith +@ContextConfiguration)。 - @ContextConfiguration:指定配置来源——
classes = AppConfig.class(JavaConfig)或locations = "classpath:beans.xml"(XML)。 - 上下文缓存:相同配置的测试类共享同一个 ApplicationContext(框架级缓存,大大提速);配置不同才重建。
- 测试类里可
@Autowired注入被测 Bean、@Value取属性,配合@ActiveProfiles("test")、@TestPropertySource切环境。 - 事务回滚:
@Transactional标在测试方法/类上时,TransactionalTestExecutionListener会在方法前开事务、方法后回滚——测试写的库数据不残留;类级别的@Transactional对类内每个方法都生效。 - 想真提交:
@Rollback(false)或@Commit标注;也可在测试里注入TransactionTemplate自主控制。 - 上下文被污染时用
@DirtiesContext标记“本测试后重建上下文”(有成本,慎用)。
代码示例
@SpringJUnitConfig(classes = TestConfig.class) // 启动真实容器
@ActiveProfiles("test") // 测试 profile
@Transactional // 类级:每个测试方法事务+回滚
class AccountServiceTest {
@Autowired AccountService accountService;
@Autowired JdbcTemplate jdbcTemplate;
@Test
void transfer_should_rollback() {
accountService.transfer(1L, 2L, 50);
// 测试结束自动回滚:balance 断言只在此事务内成立,
// 方法返回后数据恢复原状,其他测试不受影响
Long balance = jdbcTemplate.queryForObject(
"SELECT balance FROM account WHERE id = 1", Long.class);
assertEquals(950L, balance);
}
@Test
@Rollback(false) // 个别用例想保留数据,显式声明
void keep_data() { /* 提交 */ }
}
// TestConfig 示意:嵌入式库数据源 + 事务管理器 + JdbcTemplate
// @Bean DataSource ds = 内嵌数据库(如 H2,需驱动依赖);
// @Bean PlatformTransactionManager = new DataSourceTransactionManager(ds);
注意点
- 回滚前提:容器里存在
PlatformTransactionManagerBean(默认按名transactionManager查找,以官方文档为准),否则@Transactional测试直接报错。 - 被测服务若吞了异常或没走代理,测试里照样验不出回滚(回滚语义在“测试方法的事务”上,不等于业务代码自动正确)。
- 上下文缓存按配置指纹匹配,随意加
@DirtiesContext会让整套测试变慢。 @BeforeEach/@AfterEach在测试事务之内执行;想在事务外准备数据请用别的手段(以官方文档为准)。- 集成测试别连生产库——测试数据管理(构造/清理)与真实事务语义要保持可控,常用独立测试库或嵌入式库。
小结
Spring 测试三板斧:@SpringJUnitConfig 起容器、@Autowired 注入被测对象、@Transactional 让测试事务自动回滚。真容器 + 回滚隔离,让“带数据库的集成测试”既能跑又互不污染。此章是前半段 Spring Core 的收尾,后半段 Web MVC 的测试仍复用同一套 TestContext。