事务管理
在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() {
// 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
确保您的事务前方法或事务后方法在适当的时间运行。
一般来说, 但是,从 Spring Framework 6.1 开始,对于使用带有 JUnit Jupiter 的
|
任何 before 方法(例如使用 JUnit Jupiter 的 同样,使用 |
配置事务管理器
TransactionalTestExecutionListener
期望在测试的 Spring ApplicationContext
中定义一个 PlatformTransactionManager
bean。如果测试的 ApplicationContext
中有多个 PlatformTransactionManager
实例,则可以使用 @Transactional("myTxMgr")
或 @Transactional(transactionManager = "myTxMgr")
声明限定符,或者可以通过 @Configuration
类实现 TransactionManagementConfigurer
。有关在测试的 ApplicationContext
中查找事务管理器的算法详细信息,请参阅TestContextTransactionUtils.retrieveTransactionManager()
的 javadoc。
所有与事务相关的注解演示
以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试场景,该场景重点介绍了所有与事务相关的注解。此示例并非旨在演示最佳实践,而是演示如何使用这些注解。有关更多信息和配置示例,请参见注解支持部分。@Sql
的事务管理包含一个附加示例,该示例使用 @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 时,除非在保存或更新实体后调用 以下示例显示了如何刷新
请参阅 Spring Framework 测试套件中的JpaEntityListenerTests,了解使用所有 JPA 生命周期回调的实际示例。 |