此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cadn.net.cn

事务管理

在 TestContext 框架中,事务由TransactionalTestExecutionListener,默认情况下会配置该 显式声明@TestExecutionListeners在你的测试类上。要启用对 transactions 的PlatformTransactionManagerbean 中的ApplicationContext加载了@ContextConfiguration语义(进一步 详细信息将在后面提供)。此外,您必须声明 Spring 的@Transactional注解。spring-doc.cadn.net.cn

测试管理的事务

测试托管事务是使用TransactionalTestExecutionListener或者使用TestTransaction(稍后介绍)。您不应将此类事务与 Spring 管理的事务混淆 事务(由 Spring 在ApplicationContext已加载 测试)或应用程序管理的事务(在 由测试调用的应用程序代码)。Spring 管理和应用程序管理 事务通常参与 test-managed 事务。但是,您应该使用 如果 Spring 管理的事务或应用程序管理的事务配置了任何 propagation type (传播类型)REQUIREDSUPPORTS(有关详细信息,请参阅有关事务传播的讨论)。spring-doc.cadn.net.cn

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

在测试框架中使用任何形式的抢占式超时时,必须小心 与 Spring 的 test-managed 事务结合使用。spring-doc.cadn.net.cn

具体来说, Spring 的测试支持将事务状态绑定到当前线程(通过 一个java.lang.ThreadLocal变量)调用。如果 testing 框架在新线程中调用当前测试方法,以支持 preemptive timeout,则在当前测试方法中执行的任何作都不会 在测试托管的事务中调用。因此,任何此类作的结果 不会使用 test-managed 事务回滚。相反,此类作 将提交到持久化存储(例如,关系数据库),甚至 尽管 Spring 正确回滚了 test-managed 事务。spring-doc.cadn.net.cn

可能发生这种情况的情况包括但不限于以下情况。spring-doc.cadn.net.cn

启用和禁用事务

使用@Transactional使测试在 transaction 的事务,默认情况下,在测试完成后自动回滚。 如果测试类带有@Transactional,该类中的每个测试方法 hierarchy 在事务中运行。未使用@Transactional(在类或方法级别)不在事务中运行。注意 那@Transactional在测试生命周期方法上不受支持,例如,方法 用 JUnit Jupiter 的@BeforeAll,@BeforeEach等。此外,测试 的@Transactional但有propagation属性设置为NOT_SUPPORTEDNEVER不在事务中运行。spring-doc.cadn.net.cn

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

valuetransactionManagerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

propagationspring-doc.cadn.net.cn

Propagation.NOT_SUPPORTEDPropagation.NEVER受支持spring-doc.cadn.net.cn

isolationspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

timeoutspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

readOnlyspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

rollbackForrollbackForClassNamespring-doc.cadn.net.cn

否:使用TestTransaction.flagForRollback()相反spring-doc.cadn.net.cn

noRollbackFornoRollbackForClassNamespring-doc.cadn.net.cn

否:使用TestTransaction.flagForCommit()相反spring-doc.cadn.net.cn

方法级生命周期方法 — 例如,使用 JUnit Jupiter 的@BeforeEach@AfterEach— 在测试托管的事务中运行。另一方面 hand、套件级和类级生命周期方法,例如,带有 JUnit Jupiter 的@BeforeAll@AfterAll和用 TestNG 的@BeforeSuite,@AfterSuite,@BeforeClass@AfterClass不在 test-managed 事务。spring-doc.cadn.net.cn

如果您需要在 transaction,您可能希望注入相应的PlatformTransactionManager到 你的 test 类,然后将其与TransactionTemplate对于程序化 事务管理。spring-doc.cadn.net.cn

以下示例演示了为 编写集成测试的常见场景 基于 Hibernate 的UserRepository:spring-doc.cadn.net.cn

@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.spring-doc.cadn.net.cn

事务回滚和提交行为

默认情况下,测试事务将在完成 测试;但是,事务提交和回滚行为可以通过声明方式进行配置 通过@Commit@Rollback附注。有关更多详细信息,请参阅 annotation support 部分中的相应条目。spring-doc.cadn.net.cn

程序化事务管理

您可以使用静态 methods 中的TestTransaction.例如,您可以使用TestTransaction在测试中 methods、before methods和 after 方法来开始或结束当前的测试托管 transaction 或配置当前测试托管的事务以进行 rollback 或 commit。 支持TestTransaction每当TransactionalTestExecutionListener已启用。spring-doc.cadn.net.cn

以下示例演示了TestTransaction.请参阅 javadoc 的TestTransaction了解更多详情。spring-doc.cadn.net.cn

@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注解。您可以注释任何voidmethod 或voiddefault 方法与以下 annotations 和TransactionalTestExecutionListener确保您的 before-transaction 方法或 after-transaction 方法在适当的时间运行。spring-doc.cadn.net.cn

一般而言@BeforeTransaction@AfterTransaction方法不得接受 any 参数。spring-doc.cadn.net.cn

但是,从 Spring Framework 6.1 开始,对于使用SpringExtension与 JUnit Jupiter,@BeforeTransaction@AfterTransaction方法可以选择 接受参数,这些参数将由任何已注册的 JUnit 解析ParameterResolver扩展名,例如SpringExtension.这意味着特定于 JUnit 的参数(如TestInfo或来自测试的ApplicationContext可提供给@BeforeTransaction@AfterTransaction方法,如下所示 例。spring-doc.cadn.net.cn

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

任何 before 方法(例如用 JUnit Jupiter 的@BeforeEach) 和任何 after 方法(例如用 JUnit Jupiter 的@AfterEach) 运行 在事务测试方法的 test-managed 事务中。spring-doc.cadn.net.cn

同样,用@BeforeTransaction@AfterTransaction仅 run 用于事务性测试方法。spring-doc.cadn.net.cn

配置事务管理器

TransactionalTestExecutionListener期望PlatformTransactionManager要成为的 bean 在 Spring 中定义ApplicationContext进行测试。如果有多个实例 之PlatformTransactionManager在测试的ApplicationContext中,您可以声明 qualifier@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer可以由@Configuration类。查阅Javadoc 为TestContextTransactionUtils.retrieveTransactionManager()有关 算法在测试的ApplicationContext.spring-doc.cadn.net.cn

所有与事务相关的 Annotation 的演示

以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试 突出显示所有与事务相关的注释的方案。该示例并非有意为之 演示最佳实践,而是演示这些注释如何 使用。有关详细信息,请参阅注释支持部分 信息和配置示例。事务管理@Sql包含一个额外的示例,该示例使用@Sql为 具有默认事务回滚语义的声明性 SQL 脚本执行。这 以下示例显示了相关的注释:spring-doc.cadn.net.cn

@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,而另一种方法正确地公开了刷新 会期:spring-doc.cadn.net.cn

// ...

@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 的匹配方法:spring-doc.cadn.net.cn

// ...

@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 代码时避免误报的注释类似,如果您的应用程序使用实体生命周期回调(还有 称为实体侦听器),请确保刷新 test 中的底层工作单元 运行该代码的方法。未能刷新清除基础工作单元可能会 导致某些生命周期回调未被调用。spring-doc.cadn.net.cn

例如,使用 JPA 时,@PostPersist,@PreUpdate@PostUpdate回调 不会调用entityManager.flush()在实体被 saved 或 updated。同样,如果实体已附加到当前工作单元 (与当前持久化上下文相关联)时,尝试重新加载实体将 不会导致@PostLoadcallback 除非entityManager.clear()在 尝试重新加载实体。spring-doc.cadn.net.cn

以下示例显示如何刷新EntityManager以确保@PostPersist在保留实体时调用回调。具有 一个@PostPersistcallback 方法已注册Person实体 例。spring-doc.cadn.net.cn

// ...

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

// ...

有关使用所有 JPA 生命周期回调的工作示例,请参见 Spring Framework 测试套件中的JpaEntityListenerTestsspring-doc.cadn.net.cn