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 それぞれを動作させるために必要なものを一通り挙げると以下のような感じ。
<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>
<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つです。
-
TransactionAwareDataSourceProxy
を使用してDataSource
をラップする。 - テスト終了時にロールバックしたいテストケースに
@Transactional
を指定する。
その他は、至って普通のテストです。以下のような使い方になるかと思います。
@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);
}
}
@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 によりコミットされてしまう、問題の実装箇所も、非常にシンプルなものとなっているため、少し手で書いてやれば、テストデータがコミットされてしまう事象も回避することは可能です。(こちらについては、少しテーマが変わるので別で書きました -> Spring Boot, Spock, DbSetup でトランザクションを使ったテスト)