在 TestContext 框架中,事务由 默认配置的 管理,即使您不这样做 在测试类上显式声明。启用对 事务,但是,您必须在 其中配置一个 bean 加载了语义(进一步 详情将在后面提供)。此外,您必须在测试的类级别或方法级别声明 Spring 的注解。TransactionalTestExecutionListener@TestExecutionListenersPlatformTransactionManagerApplicationContext@ContextConfiguration@Transactional

测试管理的事务

测试管理事务是通过使用 或以编程方式管理的事务(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆 事务(由 Spring 直接管理的事务,在加载的 tests)或应用程序管理的事务(在 由测试调用的应用程序代码)。Spring 管理和应用程序管理 事务通常参与测试管理事务。但是,您应该使用 如果 Spring 管理或应用程序管理的事务配置了任何 或以外的传播类型(有关详细信息,请参阅有关事务传播的讨论)。TransactionalTestExecutionListenerTestTransactionApplicationContextREQUIREDSUPPORTS

抢占式超时和测试管理事务

在测试框架中使用任何形式的抢占式超时时,必须小心谨慎 结合 Spring 的测试管理事务。

具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过 变量),然后调用当前测试方法。如果 测试框架在新线程中调用当前测试方法,以支持 抢占式超时,在当前测试方法中执行的任何操作都不会 在测试管理的事务中调用。因此,任何此类行为的结果 不会使用测试管理的事务进行回滚。相反,这样的行为 将提交到持久存储(例如,关系数据库),甚至 尽管 Spring 会正确回滚测试管理的事务。java.lang.ThreadLocal

可能发生这种情况的情况包括但不限于以下情况。

  • JUnit 4 的支持和规则@Test(timeout = …​)TimeOut

  • JUnit Jupiter 的类方法assertTimeoutPreemptively(…​)org.junit.jupiter.api.Assertions

  • TestNG的支持@Test(timeOut = …​)

启用和禁用事务

注释测试方法会导致测试在 默认情况下,事务在测试完成后自动回滚。 如果测试类使用 批注,则该类中的每个测试方法都使用 层次结构在事务中运行。未批注(在类或方法级别)的测试方法不会在事务中运行。注意 测试生命周期方法(例如,方法)不支持此项。 用 JUnit Jupiter 的 、 等 注释。此外,测试 注释为,但属性设置为或未在事务中运行。@Transactional@Transactional@Transactional@Transactional@BeforeAll@BeforeEach@TransactionalpropagationNOT_SUPPORTEDNEVER

表 1. 属性支持@Transactional
属性 支持测试管理事务

valuetransactionManager

是的

propagation

仅且受支持Propagation.NOT_SUPPORTEDPropagation.NEVER

isolation

timeout

readOnly

rollbackForrollbackForClassName

否:改用TestTransaction.flagForRollback()

noRollbackFornoRollbackForClassName

否:改用TestTransaction.flagForCommit()

方法级生命周期方法(例如,使用 JUnit Jupiter 的 or 注释的方法)在测试管理的事务中运行。另一方面 手级、套件级和类级生命周期方法 — 例如,用 用 TestNG 的 、 、 或 — 注释的 JUnit Jupiter 的 or 和 方法不在 测试管理事务。@BeforeEach@AfterEach@BeforeAll@AfterAll@BeforeSuite@AfterSuite@BeforeClass@AfterClass

如果需要在套件级或类级生命周期方法中运行代码 交易中,您可能希望注入相应的 你的测试类,然后将其与 for programmatic 一起使用 事务管理。PlatformTransactionManagerTransactionTemplate

请注意,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests 是针对类级别的事务支持而预配置的。

下面的示例演示了编写集成测试的常见方案 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

程序化事务管理

您可以使用静态 中的方法。例如,您可以在测试中使用 方法、之前方法和之后方法开始或结束当前测试管理 事务,或配置当前测试管理的事务以进行回滚或提交。 每当启用时,都会自动提供对的支持。TestTransactionTestTransactionTestTransactionTransactionalTestExecutionListener

下面的示例演示了 的一些功能。请参阅 用于 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@AfterTransactionvoidvoidTransactionalTestExecutionListener

一般来说,方法一定不能接受 任何参数。@BeforeTransaction@AfterTransaction

但是,从 Spring Framework 6.1 开始,对于使用 SpringExtension 和 JUnit Jupiter 的测试,方法可以选择 接受将由任何已注册的 JUnit 扩展(如 .这意味着可以向 和 方法提供特定于 JUnit 的参数(如测试的 或 bean),如下所示 例。@BeforeTransaction@AfterTransactionParameterResolverSpringExtensionTestInfoApplicationContext@BeforeTransaction@AfterTransaction

  • Java

  • Kotlin

@BeforeTransaction
void verifyInitialDatabaseState(@Autowired DataSource dataSource) {
	// Use the DataSource to verify the initial state before a transaction is started
}
@BeforeTransaction
fun verifyInitialDatabaseState(@Autowired dataSource: DataSource) {
	// Use the DataSource to verify the initial state before a transaction is started
}

任何之前的方法(例如用 JUnit Jupiter 注释的方法)和任何 在方法(例如用 JUnit Jupiter 注释的方法)运行后 在事务测试方法的测试管理事务中。@BeforeEach@AfterEach

同样,用 or 注释的方法只有 运行事务性测试方法。@BeforeTransaction@AfterTransaction

配置事务管理器

TransactionalTestExecutionListener期望 Bean 是 在春季为测试定义。如果有多个实例 的 ,你可以声明一个 使用 或 的限定符,或者可以由类实现。查阅 javadoc 对于 TestContextTransactionUtils.retrieveTransactionManager() 了解 用于在测试的 .PlatformTransactionManagerApplicationContextPlatformTransactionManagerApplicationContext@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer@ConfigurationApplicationContext

演示所有与交易相关的注释

以下基于 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 的示例测试用例中,一个方法演示了 误报,而另一种方法正确地公开了刷新 会期:

  • Java

  • Kotlin

// ...

@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();
}

// ...
// ...

@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

  • Kotlin

// ...

@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();
}

// ...
// ...

@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()
}

// ...
测试 ORM 实体生命周期回调

与测试 ORM 代码时避免误报的说明类似,如果您的应用程序使用实体生命周期回调(也 称为实体侦听器),请确保在测试中刷新基础工作单元 运行该代码的方法。未能刷新清除基础工作单元可能会 导致某些生命周期回调未被调用。

例如,使用 JPA、、 和回调时 除非在实体被调用后调用,否则不会被调用 已保存或更新。同样,如果实体已附加到当前工作单元 (与当前持久性上下文相关联),尝试重新加载实体将 不会导致回调,除非在 尝试重新加载实体。@PostPersist@PreUpdate@PostUpdateentityManager.flush()@PostLoadentityManager.clear()

以下示例演示如何刷新 以确保在保留实体时调用回调。具有 已为 例。EntityManager@PostPersist@PostPersistPerson

  • Java

  • Kotlin

// ...

@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...
}

// ...
// ...

@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 生命周期回调的工作示例。