Spring Boot + Doma2 + Junit5 + DBUnitでテストデータのRollbackをきちんとする

# はじめに
前から同僚にこの組み合わせだとテスト実施後のRollbackが上手くいかんと言われてました。
その時はJunit4使うことで解決したのですが、この度同じ組み合わせでなんとかする必要出てきたのと、これに時間使える状況だったのもあり頑張って解決した時のメモです。

# 環境
Java 11.0.3
Spring Boot 2.2.0.M4
doma-spring-boot-starter 1.1.0
spring-test-dbunit 1.3.0
dbunit 2.6.0

データベースはMariaDBを使っていますが、他のRDBでも特に問題ないと思います(未検証)。

# pom.xml
必要そうなdependencyを載せておきます。

``` pom.xml
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.seasar.doma.boot</groupId>
            <artifactId>doma-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.springtestdbunit</groupId>
            <artifactId>spring-test-dbunit</artifactId>
            <version>1.3.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.6.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
```

# テストコードを書く前の準備
いくつかBeanを作成する必要があるため、コードを書いていきます。
サードパーティーのDBのTransactionをSpring側管理出来るようにするため、`TransactionAwareDataSourceProxy`をDataSourceに設定します。

これは普通に起動された場合にも適応したいので、main側に記述します。

```
@Configuration
public class DatabaseConfig {

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties) {
        DriverManagerDataSource dataSource = new DriverManagerDataSource(dataSourceProperties.getUrl(), dataSourceProperties.getUsername(), dataSourceProperties.getPassword());

        dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
        dataSource.setSchema(dataSource.getSchema());

        return new TransactionAwareDataSourceProxy(dataSource);
    }
}
```

DaoとEntityも書く必要があるので、テストで使用するテーブルに合わせて実装します。
実際はDoma-genを使ったのですが、今回の話題とは別なので割愛します。

``` Entity
import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.GeneratedValue;
import org.seasar.doma.GenerationType;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return role;
    }

    public void setName(String name) {
        this.name = name;
    }
}
```

``` Dao
import java.util.Optional;

import org.seasar.doma.Dao;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.Update;
import org.seasar.doma.boot.ConfigAutowireable;

import com.tasogarei.User;

@Dao
@ConfigAutowireable
public interface UserDao {

    @Select
    Optional<User> selectById(Long id);

    @Insert
    int insert(MUser entity);

    @Update
    int update(User entity);

    @Delete
    int delete(User entity);
}
```

Domaの場合はSQLファイルも必要です（これもDoma-genで自動生成)
`/src/mainresources/META-INF/`配下にパッケージ名でフォルダ掘ってそこに入れます。

``` selectById.sql
select
  *
from
  user
where
  id = /* id */1
```

あと、今回はXLSXからテストデータを読み込むので、Excelで読み込むデータを作成しています。
面倒という理由でここではファイルについては割愛します。

# テストコードを書いていく
準備が出来たのでテストを書いていきます。
今回はDAOを直接コールしてその結果を確認するようなテストとします。

設定的にはほぼSpring Boot + Junit5の設定と変わりません。
変わる箇所は`@TestExecutionListeners`くらいなものでしょうか。

Excelファイルからデータを入れる場合は`@DbUnitConfiguration`も必要となります。
中身は[こちら](http://terasolunaorg.github.io/guideline/5.5.1.RELEASE/ja/UnitTest/ImplementsOfUnitTest/PreparationForTest.html)を参考に実装しました。

あとはテストメソッドに`@DatabaseSetup`を付与して読み込むファイルを記述するだけです。
`classpath`は`/src/test/resources`を指しています。

``` DBUnitTest.java
import static org.junit.jupiter.api.Assertions.*;

import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
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.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;

import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;

import com.tasogarei.UserDao;
import com.tasogarei.User;
import com.tasogarei.XlsDataSetLoader;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class, 
        DirtiesContextTestExecutionListener.class, 
        TransactionDbUnitTestExecutionListener.class
})
@DbUnitConfiguration(dataSetLoader = XlsDataSetLoader.class)
@Transactional
public class DBUnitTest {

    @Autowired
    private UserDao userDao;

    @Test
    @DatabaseSetup("classpath:dataset/demo-test.xlsx")
    public void test() {
        Optional<User> user = userDao.selectById(2L);
        assertTrue(user.isPresent());
    }
}
```

# 終わりに
書いて見返したらたいして特別な設定をしてたわけではありませんが、次やった時に困らないように残しておきます。
今、Doma使っているけど、大変なのであんまり使いたくはないのですが。