Spring Test DBUnitでのテスト

# はじめに
SpringBootで作ったWebアプリにDBUnitを使用したテストコードを実装しました。
DBUnitを使用することでJUnitでテストする時、DBのテストデータを管理することが出来ます。
当記事ではテストクラスを実行する前にテストデータをDBに投入する処理を実装します。

# 実行環境
OS：Mac
Spring Test DBUnit：spring-test-dbunit 1.3.0
DBUnit：DBUnit 2.7.3
JUnit：JUnit5.8.2
Java：Amazon Corretto 8
Spring Boot：2.6.1
Gradle：gradle6.8
IDE：intellij idea community edition 2021.1
DB：MySQL8.0

# 環境を構築
## 依存関係を追加
build.gradleに「spring-test-dbunit」「dbunit」を依存関係に追加します。
また、今回テストするアプリはDB接続にJPAを使用しています。

```groovy
dependencies {
    // ・・・
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'mysql:mysql-connector-java'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'com.github.springtestdbunit:spring-test-dbunit:1.3.0'
	testImplementation 'org.dbunit:dbunit:2.7.3'
}

test {
    useJUnitPlatform()
}
```

# テストするクラス
DBから文字列「HelloWorld」を取得するHelloWorldServiceをテストします。

```java
@Service
public class HelloWorldService {

    private HelloWorldRepository helloWorldRepository;

    @Autowired
    public HelloWorldService(HelloWorldRepository helloWorldRepository) {
        this.helloWorldRepository = helloWorldRepository;
    }

    public String getString() {
        return helloWorldRepository.findById("1").get().getName();
    }
}
```

```java
public interface HelloWorldRepository extends JpaRepository<HelloWorldEntity, String> {

}
```

```java
@Table(name = "hello_world")
@Entity
public class HelloWorldEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public HelloWorldEntity() {
    }

    @Id
    @Getter
    @Setter
    private String id;

    @Getter
    @Setter
    private String name;

}
```

HelloWorldテーブル
テスト前はテーブルを空の状態にしておき、
テスト開始前にDBUnitを使って以下の状態になるようにデータ投入します。

```
| id(PK) |    name    |
|--------|------------|
|   1    | HelloWorld |
```

# テストを書く
## リスナーを設定する
SpringでDBUnitを使用するためには、DbUnitTestExecutionListenerクラスを使用する必要があります。
そのためには、Spring testの@&#xFFF0;TestExecutionListenersを使用して設定します。

また、DBUnitのドキュメントより、DbUnitTestExecutionListenerだけでなく、
標準のSpringリスナーも含める必要があるとのことなので、ドキュメントのサンプルを参考に以下の通り設定します。

```java
import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;

@SpringBootTest
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
public class HelloWorldServiceTest {

    // ・・・
}
```

### @&#xFFF0;DependencyInjectionTestExecutionListener
テスト実行時にSpringのDIを利用できるようにする。

### @&#xFFF0;DirtiesContextTestExecutionListener
テストで使用するDIコンテナのライフサイクル管理機能を提供する。
テストクラスまたはテストメソッドの実行後に呼び出される。

### @&#xFFF0;TransactionalTestExecutionListener
テスト実行時のトランザクション管理機能を提供している。

### @&#xFFF0;DbUnitTestExecutionListener
@&#xFFF0;DatabaseSetup、@&#xFFF0;DatabaseTearDown、@&#xFFF0;ExpectedDatabaseをサポートする。

## テスト前のデータ投入準備
### @&#xFFF0;DatabaseSetup
@&#xFFF0;DatabaseSetupは、テストメソッドを実行する前にテーブルデータを指定します。
このアノテーションは各テストメソッド、またはクラス全体に指定でき、
クラスレベルで指定した場合も各テストメソッド毎にセットアップされます。

@&#xFFF0;DatabaseSetupの引数にテストデータとなるXMLファイルを指定します。
XMLファイルは以下のように記述します。

```xml
<?xml version="1.0" encoding="UTF-8"?>

<dataset>
    <テーブル名 カラム1="カラム1の内容" カラム2="カラム2の内容" />
</dataset>
```

上記をベースに今回のテストデータを以下の通りで用意します。

```xml
<?xml version="1.0" encoding="UTF-8"?>

<dataset>
    <hello_world id="1" name="HelloWorld" />
</dataset>
```

テストクラスの指定
テストデータを「/dbunit/sampleData.xml」で用意しました。
※@&#xFFF0;DatabaseSetupにファイル名だけを指定した場合、テストクラスと同一パッケージ内からXMLファイルを探します。

```java
    @Test
    @DatabaseSetup("/dbunit/sampleData.xml")
    void HelloWorldが取得されることを確認() {
            String result = helloWorldService.getString();
            assertThat(result).isEqualTo("HelloWorld");
            }
```

## テストクラス全体
テストクラス全体としては以下のようになりました。
この状態で「HelloWorldが取得されることを確認()」を実行すると
テスト前にデータが投入され、HelloWorldServiceから「HelloWorld」という文字列が返ることを検証できました。

```java
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
        })
public class HelloWorldServiceTest {

    private final HelloWorldService helloWorldService;

    @Autowired
    public HelloWorldServiceTest(HelloWorldService helloworldService) {
        this.helloWorldService = helloworldService;
    }

    @Test
    @DatabaseSetup("/dbunit/sampleData.xml")
    void HelloWorldが取得されることを確認() {
        String result = helloWorldService.getString();
        assertThat(result).isEqualTo("HelloWorld");
    }
}
```

## テストの後はテーブルをキレイにしておきたい
上記、テストクラスでテストが通ることが確認できました。
しかし、テスト実行後のテーブルを見ると投入したデータがそのままになっています。
複数のテストを書いた場合、この状態は好ましくないので、テスト完了後はデータを削除するようにします。

```java
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
        })
public class HelloWorldServiceTest {
```

テストクラスに@&#xFFF0;Transactionalを付与することでテスト後にデータを残さないように出来ました。


# 参考文献

http://dbunit.sourceforge.net/dbunit/index.html

https://springtestdbunit.github.io/spring-test-dbunit/

https://spring.pleiades.io/spring-boot/docs/current/reference/html/features.html#features.testing

https://spring.pleiades.io/spring-framework/docs/5.3.18/reference/html/testing.html#testing-introduction

https://terasolunaorg.github.io/guideline/5.4.1.RELEASE/ja/UnitTest/ImplementsOfUnitTest/UsageOfLibraryForTest.html

https://www.techscore.com/tech/Java/Others/DBUnit/4/