事务管理
在 TestContext 框架中,事务由 默认配置的 管理,即使您没有这样做
在你的测试类上显式声明。要启用对
transactions,但是,您必须在 中配置一个 bean,该 bean 加载了语义(进一步
详细信息将在后面提供)。此外,你必须在测试的类或方法级别声明 Spring 的注释。TransactionalTestExecutionListener
@TestExecutionListeners
PlatformTransactionManager
ApplicationContext
@ContextConfiguration
@Transactional
测试管理的事务
测试托管的事务是通过使用 或 using 以编程方式进行管理的事务(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆
事务(那些由 Spring 直接管理的
测试)或应用程序管理的事务(在
由测试调用的应用程序代码)。Spring 管理和应用程序管理
事务通常参与 test-managed 事务。但是,您应该使用
如果 Spring 管理的事务或应用程序管理的事务配置了任何
propagation type 而不是 or (有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListener
TestTransaction
ApplicationContext
REQUIRED
SUPPORTS
抢占式超时和测试托管事务
在测试框架中使用任何形式的抢占式超时时,必须小心 与 Spring 的 test-managed 事务结合使用。 具体来说, Spring 的测试支持将事务状态绑定到当前线程(通过
一个变量)在调用当前测试方法之前。如果
testing 框架在新线程中调用当前测试方法,以支持
preemptive timeout,则在当前测试方法中执行的任何操作都不会
在测试托管的事务中调用。因此,任何此类操作的结果
不会使用 test-managed 事务回滚。相反,此类操作
将提交到持久化存储(例如,关系数据库),甚至
尽管 Spring 正确回滚了 test-managed 事务。 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
为测试方法添加注释会导致测试在
transaction 的事务,默认情况下,在测试完成后自动回滚。
如果测试类使用 进行批注,则该类中的每个测试方法
hierarchy 在事务中运行。未注释的测试方法(在类或方法级别)不会在事务中运行。注意
这在测试生命周期方法(例如,方法)上不受支持
用 JUnit Jupiter 的 , , 等进行注释。此外,测试
被注释,但具有 set to 或 are not run 在事务中的属性。@Transactional
@Transactional
@Transactional
@Transactional
@BeforeAll
@BeforeEach
@Transactional
propagation
NOT_SUPPORTED
NEVER
属性 | 支持测试托管事务 |
---|---|
|
是的 |
|
only 和 are supported |
|
不 |
|
不 |
|
不 |
|
否:改用 |
|
否:改用 |
方法级生命周期方法(例如,使用 JUnit Jupiter 的 or 注释的方法)在测试托管的事务中运行。另一方面
hand、套件级和类级生命周期方法,例如,带有
使用 TestNG 、 、 或 — 注释的 JUnit Jupiter 的 or 和 方法不在
test-managed 事务。 如果您需要在
transaction,您可能希望将相应的
您的 test 类,然后将其与 for programmatic 一起使用
事务管理。 |
请注意,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 和 Commit Behavior 中所述,无需
在方法运行后清理数据库,因为对
数据库会自动回滚。createUser()
TransactionalTestExecutionListener
事务回滚和提交行为
默认情况下,测试事务将在完成
测试;但是,事务提交和回滚行为可以通过声明方式进行配置
通过 和 注释。有关更多详细信息,请参阅 annotation support 部分中的相应条目。@Commit
@Rollback
程序化事务管理
您可以使用静态
方法。例如,您可以在 test 中使用
methods、before methods和 after 方法来开始或结束当前的测试托管
transaction 或配置当前测试托管的事务以进行 rollback 或 commit。
只要启用,支持就会自动可用。TestTransaction
TestTransaction
TestTransaction
TransactionalTestExecutionListener
以下示例演示了 .请参阅
javadoc 获取更多详细信息。
TestTransaction
-
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"))
}
}
在事务外部运行代码
有时,您可能需要在事务测试之前或之后运行某些代码
方法,但在事务上下文之外,例如,要验证初始
数据库状态
行为(如果测试配置为提交事务)。 支持此类方案的 AND 注释。您可以使用以下方法之一对测试类中的任何方法或测试接口中的任何默认方法进行注释
annotations,并确保
before-transaction 方法或 after-transaction 方法在适当的时间运行。TransactionalTestExecutionListener
@BeforeTransaction
@AfterTransaction
void
void
TransactionalTestExecutionListener
一般来说,和方法一定不能接受
any 参数。 但是,从 Spring Framework 6.1 开始,对于将
|
any before 方法(例如用 JUnit Jupiter 注释的方法)和任何
在方法(例如用 JUnit Jupiter's 注释的方法)运行之后
在事务测试方法的 test-managed 事务中。 同样,使用 或 注释的方法仅
run 用于事务性测试方法。 |
配置事务管理器
TransactionalTestExecutionListener
期望 bean 为
在 Spring 中为测试定义。如果有多个实例
of 中,您可以声明一个
限定符,或者可以由类实现。查阅 javadoc
对于 TestContextTransactionUtils.retrieveTransactionManager(),
有关
算法用于在测试的 .PlatformTransactionManager
ApplicationContext
PlatformTransactionManager
ApplicationContext
@Transactional("myTxMgr")
@Transactional(transactionManager =
"myTxMgr")
TransactionManagementConfigurer
@Configuration
ApplicationContext
所有与事务相关的 Annotation 的演示
以下基于 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 状态的应用程序代码时 persistence 上下文,请确保刷新测试方法中的底层工作单元 运行该代码。未能刷新基础工作单元可能会产生 false positives:您的测试通过,但相同的代码在实时生产中引发异常 环境。请注意,这适用于维护内存单元的任何 ORM 框架 的工作。在下面基于 Hibernate 的示例测试用例中,一种方法演示了 false positive,而另一种方法正确地公开了刷新 会期:
以下示例显示了 JPA 的匹配方法:
|
测试 ORM 实体生命周期回调
与在测试 ORM 代码时避免误报的注释类似,如果您的应用程序使用实体生命周期回调(还有 称为实体侦听器),请确保刷新 test 中的底层工作单元 运行该代码的方法。未能刷新或清除基础工作单元可能会 导致某些生命周期回调未被调用。 例如,当使用 JPA、 、 和 callback 时
不会调用,除非在实体被
saved 或 updated。同样,如果实体已附加到当前工作单元
(与当前持久化上下文相关联)时,尝试重新加载实体将
not 导致回调,除非在
尝试重新加载实体。 以下示例显示如何刷新 以确保在保留实体时调用回调。具有
已为
例。
有关使用所有 JPA 生命周期回调的工作示例,请参见 Spring Framework 测试套件中的JpaEntityListenerTests。 |