In the TestContext framework, transactions are managed by the
TransactionalTestExecutionListener
, which is configured by default, even if you do not
explicitly declare @TestExecutionListeners
on your test class. To enable support for
transactions, however, you must configure a PlatformTransactionManager
bean in the
ApplicationContext
that is loaded with @ContextConfiguration
semantics (further
details are provided later). In addition, you must declare Spring’s @Transactional
annotation either at the class or the method level for your tests.
Test-managed transactions are transactions that are managed declaratively by using the
TransactionalTestExecutionListener
or programmatically by using TestTransaction
(described later). You should not confuse such transactions with Spring-managed
transactions (those managed directly by Spring within the ApplicationContext
loaded for
tests) or application-managed transactions (those managed programmatically within
application code that is invoked by tests). Spring-managed and application-managed
transactions typically participate in test-managed transactions. However, you should use
caution if Spring-managed or application-managed transactions are configured with any
propagation type other than REQUIRED
or SUPPORTS
(see the discussion on
transaction propagation for details).
Warning
|
Preemptive timeouts and test-managed transactions
Caution must be taken when using any form of preemptive timeouts from a testing framework in conjunction with Spring’s test-managed transactions. Specifically, Spring’s testing support binds transaction state to the current thread (via
a Situations in which this can occur include but are not limited to the following.
|
Annotating a test method with @Transactional
causes the test to be run within a
transaction that is, by default, automatically rolled back after completion of the test.
If a test class is annotated with @Transactional
, each test method within that class
hierarchy runs within a transaction. Test methods that are not annotated with
@Transactional
(at the class or method level) are not run within a transaction. Note
that @Transactional
is not supported on test lifecycle methods — for example, methods
annotated with JUnit Jupiter’s @BeforeAll
, @BeforeEach
, etc. Furthermore, tests that
are annotated with @Transactional
but have the propagation
attribute set to
NOT_SUPPORTED
or NEVER
are not run within a transaction.
@Transactional
attribute support
Attribute | Supported for test-managed transactions |
---|---|
|
yes |
|
only |
|
no |
|
no |
|
no |
|
no: use |
|
no: use |
Tip
|
Method-level lifecycle methods — for example, methods annotated with JUnit Jupiter’s
If you need to run code in a suite-level or class-level lifecycle method within a
transaction, you may wish to inject a corresponding |
Note that AbstractTransactionalJUnit4SpringContextTests
and
AbstractTransactionalTestNGSpringContextTests
are preconfigured for transactional support at the class level.
The following example demonstrates a common scenario for writing an integration test for
a Hibernate-based UserRepository
:
- Java
-
@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")); } }
- Kotlin
-
@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")) } }
As explained in Transaction Rollback and Commit Behavior, there is no need to
clean up the database after the createUser()
method runs, since any changes made to the
database are automatically rolled back by the TransactionalTestExecutionListener
.
By default, test transactions will be automatically rolled back after completion of the
test; however, transactional commit and rollback behavior can be configured declaratively
via the @Commit
and @Rollback
annotations. See the corresponding entries in the
annotation support section for further details.
You can interact with test-managed transactions programmatically by using the static
methods in TestTransaction
. For example, you can use TestTransaction
within test
methods, before methods, and after methods to start or end the current test-managed
transaction or to configure the current test-managed transaction for rollback or commit.
Support for TestTransaction
is automatically available whenever the
TransactionalTestExecutionListener
is enabled.
The following example demonstrates some of the features of TestTransaction
. See the
javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[TestTransaction
]
for further details.
- Java
-
@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")); } }
- Kotlin
-
@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")) } }
Occasionally, you may need to run certain code before or after a transactional test
method but outside the transactional context — for example, to verify the initial
database state prior to running your test or to verify expected transactional commit
behavior after your test runs (if the test was configured to commit the transaction).
TransactionalTestExecutionListener
supports the @BeforeTransaction
and
@AfterTransaction
annotations for exactly such scenarios. You can annotate any void
method in a test class or any void
default method in a test interface with one of these
annotations, and the TransactionalTestExecutionListener
ensures that your before
transaction method or after transaction method runs at the appropriate time.
Tip
|
Any before methods (such as methods annotated with JUnit Jupiter’s @BeforeEach )
and any after methods (such as methods annotated with JUnit Jupiter’s @AfterEach ) are
run within a transaction. In addition, methods annotated with @BeforeTransaction or
@AfterTransaction are not run for test methods that are not configured to run within a
transaction.
|
TransactionalTestExecutionListener
expects a PlatformTransactionManager
bean to be
defined in the Spring ApplicationContext
for the test. If there are multiple instances
of PlatformTransactionManager
within the test’s ApplicationContext
, you can declare a
qualifier by using @Transactional("myTxMgr")
or @Transactional(transactionManager =
"myTxMgr")
, or TransactionManagementConfigurer
can be implemented by an
@Configuration
class. Consult the
{api-spring-framework}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
for TestContextTransactionUtils.retrieveTransactionManager()
] for details on the
algorithm used to look up a transaction manager in the test’s ApplicationContext
.
The following JUnit Jupiter based example displays a fictitious integration testing
scenario that highlights all transaction-related annotations. The example is not intended
to demonstrate best practices but rather to demonstrate how these annotations can be
used. See the annotation support section for further
information and configuration examples. Transaction management for @Sql
contains an additional example that uses @Sql
for
declarative SQL script execution with default transaction rollback semantics. The
following example shows the relevant annotations:
- Java
-
@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 } }
- Kotlin
-
@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 } }
Note
|
Avoid false positives when testing ORM code
When you test application code that manipulates the state of a Hibernate session or JPA persistence context, make sure to flush the underlying unit of work within test methods that run that code. Failing to flush the underlying unit of work can produce false positives: Your test passes, but the same code throws an exception in a live, production environment. Note that this applies to any ORM framework that maintains an in-memory unit of work. In the following Hibernate-based example test case, one method demonstrates a false positive, and the other method correctly exposes the results of flushing the session:
The following example shows matching methods for JPA:
|
Note
|
Testing ORM entity lifecycle callbacks
Similar to the note about avoiding false positives when testing ORM code, if your application makes use of entity lifecycle callbacks (also known as entity listeners), make sure to flush the underlying unit of work within test methods that run that code. Failing to flush or clear the underlying unit of work can result in certain lifecycle callbacks not being invoked. For example, when using JPA, The following example shows how to flush the
See JpaEntityListenerTests in the Spring Framework test suite for working examples using all JPA lifecycle callbacks. |