事务管理
在TestContext框架中,事务由TransactionalTestExecutionListener
管理,默认配置,即使您在测试类中没有显式声明@TestExecutionListeners
。但是,要启用事务支持,您必须在使用@ContextConfiguration
语义加载的ApplicationContext
中配置一个PlatformTransactionManager
bean(稍后提供更多详细信息)。此外,您必须在类或方法级别为您的测试声明Spring的@Transactional
注解。
测试管理的事务
测试管理的事务是通过使用TransactionalTestExecutionListener
声明性管理或通过使用TestTransaction
(稍后描述)进行程序化管理的事务。您不应将这种事务与Spring管理的事务(由Spring直接在为测试加载的ApplicationContext
中管理)或应用程序管理的事务(在测试调用的应用程序代码中进行程序化管理)混淆。Spring管理的事务和应用程序管理的事务通常参与测试管理的事务。但是,如果Spring管理的事务或应用程序管理的事务配置为除REQUIRED
或SUPPORTS
之外的传播类型,请谨慎使用(有关详细信息,请参阅关于事务传播的讨论)。
预防性超时和测试管理的事务
在使用任何形式的测试框架中的预防性超时与Spring的测试管理事务结合时,必须小心。 具体而言,Spring的测试支持将事务状态绑定到当前线程(通过 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
使用@Transactional
注解测试方法会导致测试在事务中运行,默认情况下,在测试完成后会自动回滚事务。如果测试类使用@Transactional
注解,那么该类层次结构中的每个测试方法都会在事务中运行。未使用@Transactional
注解(在类或方法级别)的测试方法不会在事务中运行。请注意,@Transactional
不支持测试生命周期方法,例如使用JUnit Jupiter的@BeforeAll
、@BeforeEach
等注解的方法。此外,使用@Transactional
注解但将propagation
属性设置为NOT_SUPPORTED
或NEVER
的测试方法不会在事务中运行。
属性 | 支持测试管理的事务 |
---|---|
|
是 |
|
仅支持 |
|
否 |
|
否 |
|
否 |
|
否:请使用 |
|
否:请使用 |
方法级生命周期方法,例如使用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"))
}
}
如在事务回滚和提交行为中所述,在createUser()
方法运行后无需清理数据库,因为对数据库的任何更改都会被TransactionalTestExecutionListener
自动回滚。
事务回滚和提交行为
默认情况下,测试事务会在测试完成后自动回滚;但是,事务提交和回滚行为可以通过@Commit
和@Rollback
注解进行声明式配置。有关更多详细信息,请参阅注解支持部分中的相应条目。
编程式事务管理
您可以通过使用TestTransaction
中的静态方法来以编程方式与测试管理的事务进行交互。例如,您可以在测试方法、before方法和after方法中使用TestTransaction
来启动或结束当前的测试管理事务,或者配置当前的测试管理事务以进行回滚或提交。只要启用了TransactionalTestExecutionListener
,就会自动支持TestTransaction
。
以下示例演示了TestTransaction
的一些特性。有关更多详细信息,请参阅TestTransaction
的javadoc。
-
Java
-
Kotlin
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// 在测试数据库中断言初始状态:
assertNumUsers(2);
deleteFromTables("user");
// 对数据库的更改将被提交!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// 执行针对数据库的其他操作,这些操作将在测试完成后自动回滚...
}
protected void assertNumUsers(int expected) {
assertEquals("在[user]表中的行数。", expected, countRowsInTable("user"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun transactionalTest() {
// 在测试数据库中断言初始状态:
assertNumUsers(2)
deleteFromTables("user")
// 对数据库的更改将被提交!
TestTransaction.flagForCommit()
TestTransaction.end()
assertFalse(TestTransaction.isActive())
assertNumUsers(0)
TestTransaction.start()
// 执行针对数据库的其他操作,这些操作将在测试完成后自动回滚...
}
protected fun assertNumUsers(expected: Int) {
assertEquals("在[user]表中的行数。", expected, countRowsInTable("user"))
}
}
在事务之外运行代码
偶尔,您可能需要在事务性测试方法之前或之后运行某些代码,但在事务上下文之外,例如,在运行测试之前验证初始数据库状态或在测试运行后验证预期的事务提交行为(如果测试配置为提交事务)。TransactionalTestExecutionListener
支持@BeforeTransaction
和@AfterTransaction
注解,用于处理这种情况。您可以在测试类中的任何void
方法或测试接口中的任何void
默认方法上注释这些注解,TransactionalTestExecutionListener
会确保您的before-transaction方法或after-transaction方法在适当的时间运行。
一般来说, 然而,从Spring Framework 6.1开始,对于使用JUnit Jupiter的
|
任何before方法(例如使用JUnit Jupiter的 同样,使用 |
配置事务管理器
TransactionalTestExecutionListener
期望在测试的Spring ApplicationContext
中定义一个PlatformTransactionManager
bean。如果测试的ApplicationContext
中有多个PlatformTransactionManager
实例,您可以通过使用@Transactional("myTxMgr")
或@Transactional(transactionManager = "myTxMgr")
进行限定,或者可以通过@Configuration
类实现TransactionManagementConfigurer
。请参阅javadoc中的TestContextTransactionUtils.retrieveTransactionManager()
,了解用于查找测试的ApplicationContext
中事务管理器的算法的详细信息。
所有与事务相关的注解演示
以下基于JUnit Jupiter的示例展示了一个虚构的集成测试场景,突出显示了所有与事务相关的注解。该示例并不旨在展示最佳实践,而是展示这些注解如何使用。有关更多信息和配置示例,请参阅注解支持部分。使用@Sql
进行声明性SQL脚本执行的事务管理包含了一个额外的示例,使用@Sql
进行具有默认事务回滚语义的声明性SQL脚本执行。以下示例展示了相关的注解:
-
Java
-
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// 在事务开始之前验证初始状态的逻辑
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// 在事务内设置测试数据
}
@Test
// 覆盖类级别的@Commit设置
@Rollback
void modifyDatabaseWithinTransaction() {
// 使用测试数据并修改数据库状态的逻辑
}
@AfterEach
void tearDownWithinTransaction() {
// 在事务内运行“拆除”逻辑
}
@AfterTransaction
void verifyFinalDatabaseState() {
// 在事务回滚后验证最终状态的逻辑
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// 在事务开始之前验证初始状态的逻辑
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// 在事务内设置测试数据
}
@Test
// 覆盖类级别的@Commit设置
@Rollback
fun modifyDatabaseWithinTransaction() {
// 使用测试数据并修改数据库状态的逻辑
}
@AfterEach
fun tearDownWithinTransaction() {
// 在事务内运行“拆除”逻辑
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// 在事务回滚后验证最终状态的逻辑
}
}
测试ORM代码时避免误报
当测试应用程序代码操作Hibernate会话或JPA持久性上下文的状态时,请确保在运行该代码的测试方法中刷新底层工作单元。未刷新底层工作单元可能导致误报:您的测试通过了,但相同的代码在生产环境中抛出异常。请注意,这适用于任何维护内存中工作单元的ORM框架。在以下基于Hibernate的示例测试用例中,一个方法展示了一个误报,另一个方法正确地展示了刷新会话的结果:
以下示例展示了JPA的匹配方法:
|
Testing ORM entity lifecycle callbacks
Similar to the note about avoiding false positives when testing ORM code, if your application makes use of entity lifecycle callbacks (also known as entity listeners), make sure to flush the underlying unit of work within test methods that run that code. Failing to flush or clear the underlying unit of work can result in certain lifecycle callbacks not being invoked. For example, when using JPA, The following example shows how to flush the
See JpaEntityListenerTests in the Spring Framework test suite for working examples using all JPA lifecycle callbacks. |