Spring Boot, Spock, DBUnit でトランザクションを使ったテスト

Spring では、テスト対象の内部だけでなく、テストそのものの前後にもトランザクションを使用できます。テストデータを作成するライブラリのひとつである、DBUnit を使用することで、テストデータの挿入についても、同じトランザクションで管理できるようになります。

テスト対象メソッドの Propagation 設定にもよりますが、多くの場合設定されているであろう `Propagation.REQUIRED` (`@Transactional` アノテーションでのデフォルト設定値) を使用していれば、テストで開始したトランザクションを、テスト対象にも引き継ぐことができ、テスト対象メソッドの内部でデータに変更が生じたとしても、テスト終了時にこの変更をロールバックすることができるようになります。

# 環境

- Java 1.8.0_91
- Maven 3.3.9 (Maven wrapper)
- Spring Boot 1.4.0.RELEASE
- Groovy 2.4.7
- Spock 1.1-groovy-2.4-rc-3
- DBUnit 2.5.3

```
$ ./mvnw -version
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T01:41:47+09:00)
Maven home: /Users/yo1000/.m2/wrapper/dists/apache-maven-3.3.9-bin/2609u9g41na2l7ogackmif6fj2/apache-maven-3.3.9
Java version: 1.8.0_91, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.11.5", arch: "x86_64", family: "mac"
```

# 依存関係

Spring Boot によるテスト、Spock、DBUnit それぞれを動作させるために必要なものを一通り挙げると以下のような感じ。

```xml:pom.xml&nbsp;(必要箇所のみ抜粋)
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <version>1.4.0.RELEASE</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy-all</artifactId>
  <version>2.4.7</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-core</artifactId>
  <version>1.1-groovy-2.4-rc-3</version>
  <scope>test</scope>
</dependency>
  <dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-spring</artifactId>
  <version>1.1-groovy-2.4-rc-3</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.dbunit</groupId>
  <artifactId>dbunit</artifactId>
  <version>2.5.3</version>
  <scope>test</scope>
</dependency>
```

```xml:pom.xml&nbsp;(必要箇所のみ抜粋)
<plugin>
  <groupId>org.codehaus.gmavenplus</groupId>
  <artifactId>gmavenplus-plugin</artifactId>
  <version>1.5</version>
  <executions>
    <execution>
      <goals>
        <goal>addTestSources</goal>
        <goal>testCompile</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <sources>
      <fileset>
        <directory>${pom.basedir}/src/test/groovy</directory>
        <includes>
          <include>**/*.groovy</include>
        </includes>
      </fileset>
    </sources>
  </configuration>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.19.1</version>
  <configuration>
    <includes>
      <include>**/*Test.java</include>
      <include>**/*Spec.java</include>
      <include>**/*Tests.java</include>
      <include>**/*Specs.java</include>
    </includes>
  </configuration>
</plugin>
```

# テスト

ここからは実際の書き方について。行うことは以下の2つです。

1. `TransactionAwareDataSourceProxy` を使用して `DataSource` をラップする。
1. テスト終了時にロールバックしたいテストケースに `@Transactional` を指定する。

その他は、至って普通のテストです。以下のような使い方になるかと思います。

```java:Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    @Autowired
    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);
    }
}
```

```groovy:TransactionalServiceSpec.groovy
@Unroll
@SpringBootTest
class TransactionalServiceSpec extends Specification {
    @Autowired
    DataSource dataSource

    @Transactional
    def "テスト終了時にテストデータとテスト対象による変更をロールバック"() {
        setup:
        def tester = new DataSourceDatabaseTester(dataSource)
        // テストデータの挿入

        when:
        // テスト対象を実行

        then:
        // テーブルの内容を検証
        assert ..
    }

    @Transactional
    def "テスト終了時にテスト対象による変更をロールバック"() {
        when:
        // テスト対象を実行

        then:
        // テーブルの内容を検証
        assert ..
    }
}
```

# DbSetup でのトランザクション

今回は、DBUnit によるトランザクションを使ったテストを紹介しましたが、このところ知名度の増してきた DbSetup (http://dbsetup.ninja-squad.com/) では、今回の方法を一部使用することができません。これは DbSetup では、テストデータの挿入時にコミットまで行ってしまうためで、こうなってしまうとトランザクションをかけていようが、変更は残ってしまうことになります。

ただしこれは、テストデータの挿入に限った話なので、テスト対象メソッド内でデータに変更が加えられた場合に関しては、DbSetup でも問題なくロールバックしてくれます。

また、DbSetup によりコミットされてしまう、問題の[実装箇所](https://github.com/Ninja-Squad/DbSetup/blob/master/DbSetup-core/src/main/java/com/ninja_squad/dbsetup/DbSetup.java#L108)も、非常にシンプルなものとなっているため、少し手で書いてやれば、テストデータがコミットされてしまう事象も回避することは可能です。(こちらについては、少しテーマが変わるので別で書きました -> [Spring Boot, Spock, DbSetup でトランザクションを使ったテスト](http://qiita.com/yo1000/items/a7c0825035b4fbcfa3b2))