仕事でJUnit5を使用しつつテストコードを実装していたが、@Transactional
を使用してもテスト後にDBがロールバックしない問題にぶつかってしまった。
リファレンスを見る限りだと @Transactional
を使えばロールバックできるはずなので原因が分からないままだったが、別のドキュメントを見る限りテストで RANDOM_PORT
を使用しているのが原因なのが分かった。
これを使うとサーバー側のスレッドとリクエストを投げるスレッドが別々で起動するため、 @Transactional
を使ってもロールバックしない状態になるらしい。なんとなく別スレッドで起動するんだろうなとは思いつつドキュメントを見つけられない以上よく分からないままだったが、上記ドキュメントで一応理解することが出来た。
では RANDOM_PORT
を使用すると絶対にロールバックできないのかというと別にそうでもないらしく、 DirtiesContext
というのを使えば一応は出来るらしい。
DirtiesContext (Spring Framework 5.3.21 API)
こいつを使うとテスト後にコンテキストを再ロードしたりすることが出来るらしく、これにより無理やりDBをロールバックさせることが出来た。以下は確認に使用した仮のテストコード(サーバー側のコードは省略)。
@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class CustomerControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate testRestTemplate; @DisplayName("DBロールバック確認テスト") @Nested @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class Save { @Test public void case1() { String url = UriComponentsBuilder.fromHttpUrl("http://localhost").port(port) .pathSegment("api", "customers").toUriString(); CreateCustomerRequest request = new CreateCustomerRequest("firstName1", "lastName1"); testRestTemplate.postForEntity(url, request, CustomerResponse.class); RequestEntity<Void> requestEntity = RequestEntity.get(url).build(); ResponseEntity<List<CustomerResponse>> responseEntity = testRestTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {}); System.out.println(responseEntity.getBody()); Assertions.assertNotNull(responseEntity.getBody()); Assertions.assertEquals(1, responseEntity.getBody().size()); } @Test public void case2() { String url = UriComponentsBuilder.fromHttpUrl("http://localhost").port(port) .pathSegment("api", "customers").toUriString(); CreateCustomerRequest request = new CreateCustomerRequest("firstName2", "lastName2"); testRestTemplate.postForEntity(url, request, CustomerResponse.class); RequestEntity<Void> requestEntity = RequestEntity.get(url).build(); ResponseEntity<List<CustomerResponse>> responseEntity = testRestTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {}); System.out.println(responseEntity.getBody()); Assertions.assertNotNull(responseEntity.getBody()); Assertions.assertEquals(1, responseEntity.getBody().size()); } } }
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
をコメントアウトすると2つ目のテストコード実行時に CustomerResponse
が2つになって返ってくる = 前のテストで保存した情報が残っているのが分かるが、コメントアウトせずに実行すると直前に保存したものだけが返ってくるようになる。
ただ問題としてコンテキストの再ロードには時間がかかるらしく基本的にあまり使用するものではないと思うので、これを利用する場合は対象を限定して実行するか初めからロールバックを期待しないテストコードを書くのが望ましいだろう。