|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
事务管理
在TestContext框架中,事务由TransactionalTestExecutionListener管理,默认情况下已经配置了它,即使你没有在测试类上显式声明@TestExecutionListeners。为了启用事务支持,你必须在用@ContextConfiguration语义加载的ApplicationContext中配置一个PlatformTransactionManager bean。此外,你必须在类或方法级别为你的测试声明Spring的@Transactional注解。
测试管理的事务
Test-managed transactions 是通过使用 TransactionalTestExecutionListener 声明式管理的事务,或者通过使用 TestTransaction 程序式管理的事务(稍后描述)。你不应该将这些事务与 Spring 管理的事务(直接由 Spring 在为测试加载的 ApplicationContext 中管理的事务)或应用程序管理的事务(在被测试调用的应用程序代码中程序式管理的事务)混淆。Spring 管理的事务和应用程序管理的事务通常参与 Test-managed transactions。但是,如果你配置了任何传播类型而不是 REQUIRED 或 SUPPORTS(请参阅事务传播 的讨论以获取详细信息)。
|
预占式超时和测试管理的事务
在使用任何形式的预设超时机制(来自测试框架)与Spring的测试管理事务结合时必须小心。 Specifically, Spring 的测试支持在调用当前测试方法之前将事务状态绑定到当前线程(通过 这种情况包括但不限于以下几种。
|
启用和禁用事务
使用 @Transactional 注解测试方法会导致该测试在事务中运行,该事务默认在测试完成后自动回滚。
如果一个测试类使用 @Transactional 注解,则该类层次结构中的每个测试方法都在事务中运行。未使用 @Transactional 注解的测试方法(在类级别或方法级别)不在事务中运行。请注意,@Transactional 不适用于测试生命周期方法——例如,使用 JUnit Jupiter 的 @BeforeAll、@BeforeEach 等注解的方法。此外,使用 @Transactional 注解但将 propagation 属性设置为 NOT_SUPPORTED 或 NEVER 的测试不会在事务中运行。
| 属性 | 支持测试管理的事务 |
|---|---|
|
是 |
|
仅支持 |
|
no |
|
no |
|
no |
|
使用 |
|
使用 |
|
方法级别的生命周期方法——例如,使用JUnit Jupiter的 如果你需要在事务中运行套件级或类级的生命周期方法中的代码,你可能希望将相应的 |
请注意,AbstractTransactionalJUnit4SpringContextTests 和
AbstractTransactionalTestNGSpringContextTests
在类级别已预配置为支持事务。
以下示例演示了为基于Hibernate的UserRepository编写集成测试的常见场景:
-
Java
-
Kotlin
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
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);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
lateinit var repository: HibernateUserRepository
@Autowired
lateinit var sessionFactory: SessionFactory
lateinit var jdbcTemplate: JdbcTemplate
@Autowired
fun setDataSource(dataSource: DataSource) {
this.jdbcTemplate = JdbcTemplate(dataSource)
}
@Test
fun createUser() {
// track initial state in test database:
val count = countRowsInTable("user")
val user = User()
repository.save(user)
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush()
assertNumUsers(count + 1)
}
private fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
private fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
如Transaction Rollback and Commit Behavior中所述,无需在createUser()方法运行后清理数据库,因为对数据库所做的任何更改都会被TransactionalTestExecutionListener自动回滚。
事务回滚和提交行为
默认情况下,测试事务将在测试完成后自动回滚;但是,可以通过@Commit和@Rollback注解声明式配置事务的提交和回滚行为。有关详细信息,请参阅注解支持部分。
程序化事务管理
你可以通过使用TestTransaction中的静态方法来编程式地与测试管理的事务进行交互。例如,你可以在测试方法、前置方法和后置方法中使用TestTransaction来开始或结束当前的测试管理事务,或者配置当前的测试管理事务以回滚或提交。每当启用TransactionalTestExecutionListener时,对TestTransaction的支持会自动可用。
以下示例演示了TestTransaction的一些功能。有关详细信息,请参阅TestTransaction的Javadoc。
-
Java
-
Kotlin
@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"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun 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 fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
在事务外部运行代码
偶尔,您可能需要在事务性测试方法之前或之后运行某些代码,但在事务上下文之外——例如,在运行测试之前验证初始数据库状态,或者在测试运行后验证预期的事务提交行为(如果测试被配置为提交事务)。TransactionalTestExecutionListener 支持 @BeforeTransaction 和 @AfterTransaction 注解,用于处理此类场景。您可以在测试类中的任何 void 方法或测试接口中的任何 void 默认方法上使用这些注解,并且 TransactionalTestExecutionListener 确保您的前置事务方法或后置事务方法在适当的时间运行。
任何前置方法(例如使用JUnit Jupiter的@BeforeEach注解的方法)
和任何后置方法(例如使用JUnit Jupiter的@AfterEach注解的方法)都在事务中运行。此外,使用@BeforeTransaction或@AfterTransaction注解的方法不会为未配置在事务中运行的测试方法执行。 |
配置事务管理器
TransactionalTestExecutionListener 期望在 Spring ApplicationContext 中定义一个 PlatformTransactionManager bean 用于测试。如果在测试的 ApplicationContext 中有多个 PlatformTransactionManager 实例,你可以通过使用 @Transactional("myTxMgr") 或 @Transactional(transactionManager =
"myTxMgr"),或实现 TransactionManagementConfigurer 接口的 @Configuration 类来声明限定符。
请参阅 javadoc
for TestContextTransactionUtils.retrieveTransactionManager() 以获取在测试的 ApplicationContext 中查找事务管理器所使用的算法的详细信息。
所有与事务相关的注解的演示
以下基于JUnit Jupiter的示例展示了一个虚构的集成测试场景,该场景突出了所有与事务相关的注解。此示例并非旨在演示最佳实践,而是演示如何使用这些注解。有关更多信息和配置示例,请参阅注解支持部分。0的事务管理包含一个额外的示例,该示例使用@Sql进行声明式SQL脚本执行,并具有默认的事务回滚语义。以下示例显示了相关的注解:
-
Java
-
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
fun modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
fun tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
|
避免在测试ORM代码时出现误报
当你测试操作Hibernate会话或JPA持久化上下文状态的应用代码时,请确保在运行该代码的测试方法中刷新底层的工作单元。不刷新底层的工作单元可能会产生假阳性:你的测试通过了,但在实际的生产环境中相同的代码会抛出异常。请注意,这适用于任何维护内存中工作单元的ORM框架。在下面基于Hibernate的示例测试用例中,一个方法展示了假阳性,而另一个方法正确地暴露了刷新会话的结果:
以下示例展示了JPA的匹配方法:
|
|
测试 ORM 实体生命周期回调
与在测试ORM代码时避免出现 误报 的注意事项类似,如果您的应用程序使用了实体生命周期回调(也称为实体监听器),请确保在运行该代码的测试方法中刷新底层的工作单元。未能 刷新 或 清除 底层的工作单元可能导致某些生命周期回调未被调用。 例如,当使用JPA时,除非在实体被保存或更新后调用了 以下示例说明如何将
查看 JpaEntityListenerTests 中使用所有JPA生命周期回调的Spring框架测试套件中的示例。 |