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

事务管理

在 TestContext 框架中,事务由TransactionalTestExecutionListener,默认情况下配置,即使您没有 显式声明@TestExecutionListeners在测试类上。启用对 事务,但是,您必须配置PlatformTransactionManagerbean 中的ApplicationContext加载了@ContextConfiguration语义(进一步 详细信息将在后面提供)。此外,您必须声明 Spring 的@Transactional在类或方法级别进行测试的注释。spring-doc.cadn.net.cn

测试管理的事务

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

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

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

具体来说,Spring 的测试支持将事务状态绑定到当前线程(通过 一个java.lang.ThreadLocalvariable) 在调用当前测试方法之前。如果 testing framework 在新线程中调用当前测试方法,以支持 抢占超时,则在当前测试方法中执行的任何作都不会 在测试管理的事务中调用。因此,任何此类行为的结果 不会随测试管理的事务一起回滚。相反,这样的行为 将提交到持久存储(例如,关系数据库),甚至 尽管测试管理的事务被 Spring 正确回滚。spring-doc.cadn.net.cn

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

启用和禁用事务

使用@Transactional导致测试在 事务,默认情况下,在测试完成后自动回滚。 如果测试类的注释为@Transactional,则该类中的每个测试方法 层次结构在事务中运行。未注释的测试方法@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 木星的@BeforeAll@AfterAll以及用 TestNG 的@BeforeSuite,@AfterSuite,@BeforeClass@AfterClass在 测试管理的事务。spring-doc.cadn.net.cn

如果您需要在 transaction,不妨注入一个对应的PlatformTransactionManager到 测试类,然后将其与TransactionTemplate用于程序化 事务管理。spring-doc.cadn.net.cn

以下示例演示了编写集成测试的常见场景 基于休眠的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"))
	}
}

事务回滚和提交行为中所述, 在createUser()方法运行, 由于对数据库所做的任何更改都会自动回滚TransactionalTestExecutionListener.spring-doc.cadn.net.cn

事务回滚和提交行为

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

程序化事务管理

您可以使用静态 方法TestTransaction.例如,您可以使用TestTransaction测试中 方法、before methods 和 after 方法来启动或结束当前测试管理 事务或配置当前测试管理事务以进行回滚或提交。 对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注解。您可以注释任何void测试类中的方法或任何void默认方法,在测试接口中使用其中之一 注释,以及TransactionalTestExecutionListener确保您的 before-transaction 方法或 after-transaction 方法在适当的时间运行。spring-doc.cadn.net.cn

一般而言@BeforeTransaction@AfterTransaction方法不得接受 任何参数。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
}

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

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

配置事务管理器

TransactionalTestExecutionListener期望PlatformTransactionManager豆子要成为 在 Spring 中定义ApplicationContext用于测试。如果有多个实例 之PlatformTransactionManager在测试的ApplicationContext,您可以声明一个 限定符,使用@Transactional("myTxMgr")@Transactional(transactionManager = "myTxMgr")TransactionManagementConfigurer可由@Configuration类。请查阅Java文档 为TestContextTransactionUtils.retrieveTransactionManager()有关 用于在测试的ApplicationContext.spring-doc.cadn.net.cn

所有与事务相关的注释的演示

以下基于 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 状态的应用程序代码时 持久性上下文,请确保刷新测试方法中的基础工作单元 运行该代码。未能刷新基础工作单元可能会产生错误 positives:您的测试通过了,但相同的代码在实时生产中抛出异常 环境。请注意,这适用于任何维护内存单元的 ORM 框架 工作。在以下基于 Hibernate 的示例测试用例中,一种方法演示了 误报,另一种方法正确暴露刷新 会期: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 代码时避免误报的说明类似,如果您的应用程序使用实体生命周期回调( 称为实体侦听器),请确保刷新测试中的基础工作单元 运行该代码的方法。未能刷新清除基础工作单元可能会 导致某些生命周期回调未被调用。spring-doc.cadn.net.cn

例如,当使用 JPA 时,@PostPersist,@PreUpdate@PostUpdate回调 除非entityManager.flush()在实体被调用后 已保存或更新。同样,如果实体已附加到当前工作单元 (与当前持久性上下文相关联),尝试重新加载实体将 不会导致@PostLoad回调,除非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