如何让Spring中每个集成测试真正独立运行,或在测试前执行所有CommandLineRunner?
我太懂这个坑了——用@Rollback注解完全没效果,对吧?这是因为@Rollback只能管控测试方法自身Spring事务上下文里的操作,而你通过REST接口发送的请求,相当于外部客户端调用,会触发应用内部独立的事务链,和测试方法的事务根本不是一回事,自然不会被回滚。
下面给你几个实用的解决方案,按实现复杂度和隔离度排序:
方案一:测试前后手动清理数据(最简单)
直接在每个测试方法执行前后,调用数据仓库的清空方法,或者执行SQL语句清理表:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class YourIntegrationTest { @Autowired private YourDataRepository dataRepository; // 每个测试前清空数据 @BeforeEach void setUp() { dataRepository.deleteAll(); // 如果是JPA,还可以加一句重置自增ID: // dataRepository.resetSequence(); // 需自己实现这个方法,调用原生SQL } // 测试方法 @Test void testInsertDataViaRest() { // 用TestRestTemplate发送POST请求插入数据 // 断言结果 } }
如果涉及多表关联,deleteAll()可能因为外键约束报错,这时候可以用原生SQL的TRUNCATE(记得先禁用外键检查,比如MySQL),用JdbcTemplate执行:
@Autowired private JdbcTemplate jdbcTemplate; @BeforeEach void cleanUpDatabase() { // MySQL环境下先禁用外键检查 jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=0;"); jdbcTemplate.execute("TRUNCATE TABLE your_table_1;"); jdbcTemplate.execute("TRUNCATE TABLE your_table_2;"); jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=1;"); }
方案二:用@Sql注解执行清理脚本
把清理逻辑写到SQL脚本里,用Spring的@Sql注解指定在测试前执行:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 每个测试方法执行前运行清理脚本 @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "/cleanup-db.sql") public class YourIntegrationTest { // 测试方法... }
然后在src/test/resources下创建cleanup-db.sql文件:
-- PostgreSQL示例 TRUNCATE TABLE your_table_1, your_table_2 RESTART IDENTITY CASCADE; -- MySQL示例 SET FOREIGN_KEY_CHECKS=0; TRUNCATE TABLE your_table_1; TRUNCATE TABLE your_table_2; SET FOREIGN_KEY_CHECKS=1;
方案三:用Testcontainers实现完全隔离(最彻底)
如果你的项目已经在用Testcontainers管理测试数据库,那可以让每个测试都重置容器状态,甚至重新创建容器(代价是测试速度稍慢,但隔离性拉满):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers public class YourIntegrationTest { @Container private static final MySQLContainer<?> mysqlContainer = new MySQLContainer<>("mysql:8.0") .withDatabaseName("test_db") .withUsername("test_user") .withPassword("test_pass"); @Autowired private JdbcTemplate jdbcTemplate; @BeforeEach void resetDatabase() { // 执行清空脚本,或者直接重新初始化数据库结构 jdbcTemplate.execute("TRUNCATE TABLE your_table_name RESTART IDENTITY;"); } // 测试方法... }
如果想要更彻底的隔离,可以把@Container的static去掉,这样每个测试都会创建新的容器,但测试时间会变长,按需选择。
为什么@Rollback没用?再补个知识点
@Rollback是和@Transactional(测试类/方法上的)配合工作的——测试方法运行在Spring事务里,结束后自动回滚。但REST请求是通过TestRestTemplate/WebTestClient发送的,这些请求会触发应用内部的新请求线程,启动独立的事务,和测试方法的事务完全无关,所以测试方法的回滚根本管不到这些外部请求产生的数据。
内容的提问来源于stack exchange,提问作者Sebastiaan van den Broek

