トランザクション管理
TestContext フレームワークでは、テストクラスで @TestExecutionListeners
を明示的に宣言しなくても、トランザクションはデフォルトで設定される TransactionalTestExecutionListener
によって管理されます。ただし、トランザクションのサポートを有効にするには、@ContextConfiguration
セマンティクスでロードされる ApplicationContext
で PlatformTransactionManager
Bean を構成する必要があります(詳細については後で説明します)。さらに、テストのクラスレベルまたはメソッドレベルで Spring の @Transactional
アノテーションを宣言する必要があります。
テスト管理されたトランザクション
テスト管理トランザクションは、TransactionalTestExecutionListener
を使用して宣言的に管理されるか、TestTransaction
(後述) を使用してプログラム的に管理されるトランザクションです。このようなトランザクションを、Spring 管理トランザクション (テスト用にロードされた ApplicationContext
内の Spring によって直接管理されるトランザクション) またはアプリケーション管理トランザクション (テストによって呼び出されるアプリケーションコード内でプログラムによって管理されるトランザクション) と混同しないでください。Spring 管理トランザクションとアプリケーション管理トランザクションは通常、テスト管理トランザクションに参加します。ただし、Spring 管理またはアプリケーション管理のトランザクションが REQUIRED
または SUPPORTS
以外の伝播型で構成されている場合は注意が必要です (詳細については、トランザクションの伝播に関する説明を参照してください)。
プリエンプティブタイムアウトとテスト管理されたトランザクション Spring のテスト管理トランザクションと組み合わせて、テストフレームワークからプリエンプティブタイムアウトを使用する場合は注意が必要です。 特に、Spring のテストサポートは、現在のテストメソッドが呼び出される前に、トランザクション状態を( これが発生する可能性のある状況には、以下が含まれますが、これらに限定されません。
|
トランザクションの有効化と無効化
テストメソッドに @Transactional
アノテーションを付けると、トランザクション内でテストが実行され、デフォルトでは、テストの完了後に自動的にロールバックされます。テストクラスに @Transactional
アノテーションが付けられている場合、そのクラス階層内の各テストメソッドはトランザクション内で実行されます。(クラスまたはメソッドレベルで) @Transactional
アノテーションが付けられていないテストメソッドは、トランザクション内で実行されません。@Transactional
は、テストライフサイクルメソッド(たとえば、JUnit Jupiter の @BeforeAll
、@BeforeEach
などでアノテーションが付けられたメソッド)ではサポートされていないことに注意してください。さらに、@Transactional
でアノテーションが付けられているが、propagation
属性が NOT_SUPPORTED
または NEVER
に設定されているテストはトランザクション内で実行されません。
属性 | テスト管理されたトランザクションでサポート |
---|---|
| はい |
|
|
| いいえ |
| いいえ |
| いいえ |
| いいえ: 代わりに |
| いいえ: 代わりに |
メソッドレベルのライフサイクルメソッド— たとえば、JUnit Jupiter の トランザクション内でスイートレベルまたはクラスレベルのライフサイクルメソッドでコードを実行する必要がある場合は、対応する |
AbstractTransactionalJUnit4SpringContextTests
および AbstractTransactionalTestNGSpringContextTests
は、クラスレベルでのトランザクションサポート用に事前構成されていることに注意してください。
次の例は、Hibernate ベースの 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"))
}
}
トランザクションのロールバックとコミットの動作で説明したように、データベースに加えられた変更は TransactionalTestExecutionListener
によって自動的にロールバックされるため、createUser()
メソッドの実行後にデータベースをクリーンアップする必要はありません。
トランザクションのロールバックとコミットの動作
デフォルトでは、テストトランザクションはテストの完了後に自動的にロールバックされます。ただし、トランザクションのコミットとロールバックの動作は、@Commit
および @Rollback
アノテーションを介して宣言的に構成できます。詳細については、アノテーションサポートセクションの対応するエントリを参照してください。
プログラムによるトランザクション管理
TestTransaction
の静的メソッドを使用して、プログラムでテスト管理されたトランザクションと対話できます。例: テストメソッド内、メソッドの前、メソッドの後に TestTransaction
を使用して、現在のテスト管理トランザクションを開始または終了したり、ロールバックまたはコミットのために現在のテスト管理トランザクションを構成したりできます。TransactionalTestExecutionListener
が有効になると、TestTransaction
のサポートが自動的に使用可能になります。
次の例は、TestTransaction
の機能の一部を示しています。詳細については、TestTransaction
(Javadoc) の javadoc を参照してください。
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"))
}
}
トランザクション外でのコードの実行
場合によっては、トランザクションテストメソッドの前後で、トランザクションコンテキストの外で特定のコードを実行する必要がある場合があります。たとえば、テストを実行する前にデータベースの初期状態を確認したり、テストの実行後に予想されるトランザクションのコミット動作を確認したりするためです (テストはトランザクションをコミットするように構成されていました)。TransactionalTestExecutionListener
は、まさにそのようなシナリオのために @BeforeTransaction
および @AfterTransaction
アノテーションをサポートします。テストクラス内の任意の void
メソッド、またはテストインターフェース内の任意の void
デフォルトメソッドにこれらのアノテーションのいずれかを付けることができ、TransactionalTestExecutionListener
はトランザクション前メソッドまたはトランザクション後メソッドが適切なタイミングで実行されることを保証します。
一般に、 ただし、Spring Framework 6.1 では、JUnit Jupiter で
|
前メソッド (JUnit Jupiter の 同様に、 |
トランザクションマネージャーの構成
TransactionalTestExecutionListener
は、テストのために PlatformTransactionManager
Bean が Spring ApplicationContext
で定義されることを期待しています。テストの ApplicationContext
内に PlatformTransactionManager
のインスタンスが複数ある場合は、@Transactional("myTxMgr")
または @Transactional(transactionManager = "myTxMgr")
を使用して修飾子を宣言するか、@Configuration
クラスによって TransactionManagementConfigurer
を実装できます。テストの ApplicationContext
でトランザクションマネージャーを検索するために使用されるアルゴリズムの詳細については、TestContextTransactionUtils.retrieveTransactionManager()
の javadoc を参照してください。
すべてのトランザクション関連のアノテーションのデモンストレーション
次の 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 コードのテスト時に誤検知を回避する Hibernate セッションまたは JPA 永続コンテキストの状態を操作するアプリケーションコードをテストするときは、そのコードを実行するテストメソッド内の基になる作業単位を必ずフラッシュしてください。基礎となる作業単位のフラッシュに失敗すると、誤検知が発生する可能性があります。テストは成功しますが、本番環境で同じコードが例外をスローします。これは、メモリ内の作業単位を維持する ORM フレームワークに適用されることに注意してください。次の Hibernate ベースのテストケースでは、1 つのメソッドが偽陽性を示し、もう 1 つのメソッドがセッションをフラッシュした結果を正しく公開しています。
次の例は、JPA の一致方法を示しています。
|
ORM エンティティライフサイクルコールバックのテスト ORM コードをテストする際の誤検知の回避に関する注意事項と同様に、アプリケーションでエンティティライフサイクルコールバック(エンティティリスナーとも呼ばれます)を使用する場合は、そのコードを実行するテストメソッド内の基になる作業単位を必ずフラッシュしてください。基になる作業単位のフラッシュまたはクリアに失敗すると、特定のライフサイクルコールバックが呼び出されない可能性があります。 例: JPA を使用する場合、エンティティが保存または更新された後に 次の例は、
すべての JPA ライフサイクルコールバックを使用した実例については、Spring Framework テストスイートの JpaEntityListenerTests [GitHub] (英語) を参照してください。 |