Transaction Management
在 TestContext 框架中,事务由 TransactionalTestExecutionListener
管理,该监听器默认情况下已配置,即使你没有在你测试类上显式申报 @TestExecutionListeners
。但是,要启用对事务的支持,你必须在使用 @ContextConfiguration
语义加载的 ApplicationContext
中配置一个 PlatformTransactionManager
bean(稍后将提供更多详细信息)。此外,你必须为你的测试在类级或方法级上申报 Spring 的 @Transactional
注解。
Test-managed Transactions
测试管理的事务是通过使用 TransactionalTestExecutionListener
声明式管理的事务,或通过使用 TestTransaction
程序化管理的事务(稍后描述)。您不应将此类事务与 Spring 管理的事务(直接由 Spring 在为测试加载的 ApplicationContext
中管理的事务)或应用程序管理的事务(在测试调用的应用程序代码中通过编程管理的事务)混淆。Spring 管理的事务和应用程序管理的事务通常参与测试管理的事务。但是,如果 Spring 管理的事务或应用程序管理的事务配置的传播类型与 REQUIRED
或 SUPPORTS
以外的任何其他传播类型,则应小心(请参阅有关 transaction propagation 的讨论以了解详情)。
在将测试框架的任何形式的抢占超时与 Spring 的测试管理事务结合使用时,必须小心。
具体来说,Spring 的测试支持在调用当前测试方法 之前 将事务状态绑定到当前线程(通过 java.lang.ThreadLocal
变量)。如果测试框架在一个新的线程中调用当前测试方法以支持抢占超时,则在当前测试方法中执行的任何操作 不会 在测试管理的事务中被调用。因此,任何此类操作的结果都不会随测试管理的事务一起回滚。相反,这些操作将提交到持久存储 中——例如关系数据库 ——即使 Spring 正确回滚了测试管理的事务也是如此。
可能发生这种情况的场景包括但不限于以下内容。
-
JUnit 4 的 `@Test(timeout = …)`支持和 `TimeOut`规则
-
JUnit Jupiter 在 `org.junit.jupiter.api.Assertions`类中的 `assertTimeoutPreemptively(…)`方法
-
TestNG 的 `@Test(timeOut = …)`支持
Enabling and Disabling Transactions
使用 @Transactional
对测试方法进行注释会导致测试在事务中运行,该事务在默认情况下会在测试完成后自动回滚。如果使用 @Transactional
对一个测试类进行注释,那么该类层次结构中的每个测试方法都在事务中运行。没有使用 @Transactional
(在类级或方法级)对进行注释的测试方法不会在事务中运行。请注意, @Transactional
不支持测试生命周期方法——例如,使用 JUnit Jupiter 的 @BeforeAll
、@BeforeEach
等进行注释的方法。此外,用 @Transactional
进行注释但将 propagation
属性设置为 NOT_SUPPORTED
或 NEVER
的测试不会在事务中运行。
Attribute | Supported for test-managed transactions |
---|---|
|
yes |
|
仅支持`Propagation.NOT_SUPPORTED`和`Propagation.NEVER` |
|
no |
|
no |
|
no |
|
no: use |
|
no: use |
方法级生命周期方法——例如,使用 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
回滚。
Transaction Rollback and Commit Behavior
默认情况下,测试事务将在测试完成后自动回滚;但是,可以通过 @Commit
和 @Rollback
注释以声明方式配置事务提交和回滚行为。有关更多详细信息,请参阅 annotation support 部分中的相应条目。
Programmatic Transaction Management
你可以使用 TestTransaction
中的静态方法以编程方式与测试管理的事务进行交互。例如,你可以在测试方法、方法之前和方法之后使用 TestTransaction
来启动或结束当前测试管理的事务,或为回滚或提交配置当前测试管理的事务。只要启用了 TransactionalTestExecutionListener
,就会自动提供对 TestTransaction
的支持。
以下示例演示了 TestTransaction
的一些功能。有关更多详细信息,请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/transaction/TestTransaction.html[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"))
}
}
Running Code Outside of a Transaction
有时,您可能需要在事务性测试方法之前或之后运行某些代码,但位于事务性上下文之外——例如,为了在运行测试之前验证初始数据库状态或在测试运行之后验证预期的事务提交行为(如果将测试配置为提交事务)。TransactionalTestExecutionListener
支持 @BeforeTransaction
和 @AfterTransaction
注释,用于支持上述场景。您可以使用这些注释之一在测试类中的任何 void
方法或者在测试接口中的任何 void
默认方法上添加注释,而 TransactionalTestExecutionListener
确保您的事务前方法或事务后方法在适当的时间运行。
一般来说, |
- Java
-
@BeforeTransaction void verifyInitialDatabaseState(@Autowired DataSource dataSource) { // Use the DataSource to verify the initial state before a transaction is started }
- Kotlin
-
@BeforeTransaction fun verifyInitialDatabaseState(@Autowired dataSource: DataSource) { // Use the DataSource to verify the initial state before a transaction is started }
任何前置方法(例如使用 JUnit Jupiter 的 |
Configuring a Transaction Manager
TransactionalTestExecutionListener
预期 ApplicationContext
用于测试的 Spring 中定义了一个 PlatformTransactionManager
bean。如果测试的 ApplicationContext
中有多个 PlatformTransactionManager
实例,则可以使用 @Transactional("myTxMgr")
或 @Transactional(transactionManager =
"myTxMgr")
声明限定符,或者可以通过一个 @Configuration
类来实现 TransactionManagementConfigurer
。查阅 TestContextTransactionUtils.retrieveTransactionManager()
的 javadoc 了解在测试的 ApplicationContext
中查找事务管理器的算法的详细信息。
Demonstration of All Transaction-related Annotations
以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试场景,该场景突出了所有与事务相关的注释。该示例的目的是演示这些注释如何使用,而不是演示最佳实践。有关详细信息和配置示例,请参阅annotation support 部分。Transaction management for @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
}
}
Avoid false positives when testing ORM code
当您测试操纵 Hibernate 会话或 JPA 持久性上下文状态的应用程序代码时,请务必在运行该代码的测试方法中刷新底层工作单元。未能刷新底层工作单元可能会产生错误肯定:您的测试通过,但相同的代码在实际生产环境中会出现异常。请注意,这适用于维护内存工作单元的任何 ORM 框架。在以下基于 Hibernate 的示例测试用例中,一种方法展示了错误肯定,而另一种方法正确地展示刷新会话的结果: |
- Java
-
// ... @Autowired SessionFactory sessionFactory; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInHibernateSession(); // False positive: an exception will be thrown once the Hibernate // Session is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithSessionFlush() { updateEntityInHibernateSession(); // Manual flush is required to avoid false positive in test sessionFactory.getCurrentSession().flush(); } // ...
- Kotlin
-
// ... @Autowired lateinit var sessionFactory: SessionFactory @Transactional @Test // no expected exception! fun falsePositive() { updateEntityInHibernateSession() // False positive: an exception will be thrown once the Hibernate // Session is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) fun updateWithSessionFlush() { updateEntityInHibernateSession() // Manual flush is required to avoid false positive in test sessionFactory.getCurrentSession().flush() } // ...
以下示例显示了 JPA 的匹配方法:
- Java
-
// ... @PersistenceContext EntityManager entityManager; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInJpaPersistenceContext(); // False positive: an exception will be thrown once the JPA // EntityManager is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithEntityManagerFlush() { updateEntityInJpaPersistenceContext(); // Manual flush is required to avoid false positive in test entityManager.flush(); } // ...
- Kotlin
-
// ... @PersistenceContext lateinit var entityManager:EntityManager @Transactional @Test // no expected exception! fun falsePositive() { updateEntityInJpaPersistenceContext() // False positive: an exception will be thrown once the JPA // EntityManager is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) void updateWithEntityManagerFlush() { updateEntityInJpaPersistenceContext() // Manual flush is required to avoid false positive in test entityManager.flush() } // ...
Testing ORM entity lifecycle callbacks
类似于有关在测试 ORM 代码时避免 false positives 的注释,如果你的应用程序利用实体生命周期回调(也称为实体侦听器),请确保在运行该代码的测试方法内刷新底层的工作单元。未能_flush_ 或_clear_ 底层的工作单元可能会导致不调用某些生命周期回调。
例如,当使用 JPA 时, |
- Java
-
// ... @Autowired JpaPersonRepository repo; @PersistenceContext EntityManager entityManager; @Transactional @Test void savePerson() { // EntityManager#persist(...) results in @PrePersist but not @PostPersist repo.save(new Person("Jane")); // Manual flush is required for @PostPersist callback to be invoked entityManager.flush(); // Test code that relies on the @PostPersist callback // having been invoked... } // ...
- Kotlin
-
// ... @Autowired lateinit var repo: JpaPersonRepository @PersistenceContext lateinit var entityManager: EntityManager @Transactional @Test fun savePerson() { // EntityManager#persist(...) results in @PrePersist but not @PostPersist repo.save(Person("Jane")) // Manual flush is required for @PostPersist callback to be invoked entityManager.flush() // Test code that relies on the @PostPersist callback // having been invoked... } // ...
请参阅 Spring Framework 测试套件中的 JpaEntityListenerTests,以获取使用所有 JPA 生命周期回调的工作示例。