在 TestContext 框架中,事务由 默认配置的 管理,即使您不这样做
在测试类上显式声明。启用对
事务,但是,您必须在 其中配置一个 bean 加载了语义(进一步
详情将在后面提供)。此外,您必须在测试的类级别或方法级别声明 Spring 的注解。TransactionalTestExecutionListener
@TestExecutionListeners
PlatformTransactionManager
ApplicationContext
@ContextConfiguration
@Transactional
测试管理的事务
测试管理事务是通过使用 或以编程方式管理的事务(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆
事务(由 Spring 直接管理的事务,在加载的
tests)或应用程序管理的事务(在
由测试调用的应用程序代码)。Spring 管理和应用程序管理
事务通常参与测试管理事务。但是,您应该使用
如果 Spring 管理或应用程序管理的事务配置了任何
或以外的传播类型(有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListener
TestTransaction
ApplicationContext
REQUIRED
SUPPORTS
抢占式超时和测试管理事务
在测试框架中使用任何形式的抢占式超时时,必须小心谨慎 结合 Spring 的测试管理事务。 具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过
变量),然后调用当前测试方法。如果
测试框架在新线程中调用当前测试方法,以支持
抢占式超时,在当前测试方法中执行的任何操作都不会
在测试管理的事务中调用。因此,任何此类行为的结果
不会使用测试管理的事务进行回滚。相反,这样的行为
将提交到持久存储(例如,关系数据库),甚至
尽管 Spring 会正确回滚测试管理的事务。 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
注释测试方法会导致测试在
默认情况下,事务在测试完成后自动回滚。
如果测试类使用 批注,则该类中的每个测试方法都使用
层次结构在事务中运行。未批注(在类或方法级别)的测试方法不会在事务中运行。注意
测试生命周期方法(例如,方法)不支持此项。
用 JUnit Jupiter 的 、 等 注释。此外,测试
注释为,但属性设置为或未在事务中运行。@Transactional
@Transactional
@Transactional
@Transactional
@BeforeAll
@BeforeEach
@Transactional
propagation
NOT_SUPPORTED
NEVER
属性 | 支持测试管理事务 |
---|---|
|
是的 |
|
仅且受支持 |
|
不 |
|
不 |
|
不 |
|
否:改用 |
|
否:改用 |
方法级生命周期方法(例如,使用 JUnit Jupiter 的 or 注释的方法)在测试管理的事务中运行。另一方面
手级、套件级和类级生命周期方法 — 例如,用
用 TestNG 的 、 、 或 — 注释的 JUnit Jupiter 的 or 和 方法不在
测试管理事务。 如果需要在套件级或类级生命周期方法中运行代码
交易中,您可能希望注入相应的
你的测试类,然后将其与 for programmatic 一起使用
事务管理。 |
请注意,AbstractTransactionalJUnit4SpringContextTests
和 AbstractTransactionalTestNGSpringContextTests
是针对类级别的事务支持而预配置的。
下面的示例演示了编写集成测试的常见方案
a 基于休眠: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
TestTransaction
TestTransaction
TransactionalTestExecutionListener
下面的示例演示了 的一些功能。请参阅
用于 TestTransaction
的 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 注解。您可以使用其中之一对测试类中的任何方法或测试接口中的任何默认方法进行注释
注释,并确保你的
事务前方法或事务后方法在适当的时间运行。TransactionalTestExecutionListener
@BeforeTransaction
@AfterTransaction
void
void
TransactionalTestExecutionListener
一般来说,方法一定不能接受
任何参数。 但是,从 Spring Framework 6.1 开始,对于使用
|
任何之前的方法(例如用 JUnit Jupiter 注释的方法)和任何
在方法(例如用 JUnit Jupiter 注释的方法)运行后
在事务测试方法的测试管理事务中。 同样,用 or 注释的方法只有
运行事务性测试方法。 |
配置事务管理器
TransactionalTestExecutionListener
期望 Bean 是
在春季为测试定义。如果有多个实例
的 ,你可以声明一个
使用 或 的限定符,或者可以由类实现。查阅 javadoc
对于 TestContextTransactionUtils.retrieveTransactionManager()
了解
用于在测试的 .PlatformTransactionManager
ApplicationContext
PlatformTransactionManager
ApplicationContext
@Transactional("myTxMgr")
@Transactional(transactionManager =
"myTxMgr")
TransactionManagementConfigurer
@Configuration
ApplicationContext
演示所有与交易相关的注释
以下基于 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 代码时出现误报
测试操作休眠会话或 JPA 状态的应用程序代码时 持久性上下文,请确保在测试方法中刷新基础工作单元 运行该代码。未能刷新基础工作单元可能会产生错误 积极因素:测试通过,但相同的代码在实时生产中引发异常 环境。请注意,这适用于维护内存单元的任何 ORM 框架 的工作。在下面基于 Hibernate 的示例测试用例中,一个方法演示了 误报,而另一种方法正确地公开了刷新 会期:
以下示例显示了 JPA 的匹配方法:
|
测试 ORM 实体生命周期回调
与测试 ORM 代码时避免误报的说明类似,如果您的应用程序使用实体生命周期回调(也 称为实体侦听器),请确保在测试中刷新基础工作单元 运行该代码的方法。未能刷新或清除基础工作单元可能会 导致某些生命周期回调未被调用。 例如,使用 JPA、、 和回调时
除非在实体被调用后调用,否则不会被调用
已保存或更新。同样,如果实体已附加到当前工作单元
(与当前持久性上下文相关联),尝试重新加载实体将
不会导致回调,除非在
尝试重新加载实体。 以下示例演示如何刷新 以确保在保留实体时调用回调。具有
已为
例。
请参阅 Spring Framework 测试套件中的 JpaEntityListenerTests,了解使用所有 JPA 生命周期回调的工作示例。 |