CLOVER🍀

That was when it all began.

Database RiderをSpring Framework(Spring Boot)と合わせて使う

これは、なにをしたくて書いたもの?

前に、Database Riderに関するエントリーを書いたことがあります。

Database Riderを試してみる - CLOVER🍀

Database RiderにはSpring Framework(Spring Boot)と組み合わせて使う機能があるようなので、試してみました。

追記)
@Transactionalと組み合わせた時に気になることがあったので、別エントリーを書きました。

Database RiderをSpring Framework(Spring Boot)と合わせて使う時に、Rider JUnit 5とRider Springのどちらを使うか? - CLOVER🍀

Database Rider

Database RiderのWebサイトは、こちらです。

Database Rider

Database Rider自体は、DbUnitJUnitを統合してデータベースのテストをしやすくするためのライブラリです。

DbUnit自体も、以前に触れたことがあります。

DbUnitを試してみる - CLOVER🍀

前はDatabase Rider単体で使ってみましたが、Spring Frameworkと組み合わせて使おうというのが今回のお題です。

Database RiderとSpring Framework

Database Riderの現時点のバージョンは1.35.0です。ドキュメントはこちら。

Database Rider Documentation

ドキュメント内に、「JUnit 5とSpring Bootを一緒に使ったサンプルがあるよ」と書かれています。

TIP: The same works for SpringBoot projects using JUnit5, see an example project here.

サンプルプロジェクトはこちら。

https://github.com/database-rider/database-rider/tree/1.35.0/rider-examples/spring-boot-dbunit-sample

実際にDatabase RiderとSpring Bootを合わせて使っているサンプルはこちら。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-examples/spring-boot-dbunit-sample/src/test/java/com/github/database/rider/springboot/SpringBootDBUnitTest.java

このサンプルでは、rider-junit5を使っています。

        <dependency>
            <groupId>com.github.database-rider</groupId>
            <artifactId>rider-junit5</artifactId>
            <version>${project.version}</version>
            <scope>test</scope>
        </dependency>

https://github.com/database-rider/database-rider/blob/1.35.0/rider-examples/spring-boot-dbunit-sample/pom.xml#L22-L27

つまり、ふつうにDatabase Riderを使うということ?…と思いつつ。

GitHubリポジトリREADME.mdを見ると、rider-springというSpring Framework向けのExtensionがあることが書かれています。

Database Rider / Spring

ところで、注意書きがありますね。このモジュールは、JUnit 4とSpringRunner向けに設計されていて、JUnit 5で使う場合は
@DBRiderアノテーションを使うこと、だそうです。

This module is designed to work with JUnit4 and SpringRunner, for JUnit5 please use @DBRider annotation from JUnit5 module, see an example here.

確かに、pom.xmlを見るとJUnit 5への依存関係は見当たりませんね。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/pom.xml

それから、現時点でSpring Frameworkのバージョンは5を対象にしています。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/pom.xml#L12

こうなると、rider-springがなにをしているのかがちょっと気になります。

@DBRiderアノテーションを使ってリスナーの指定を行い、

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/src/main/java/com/github/database/rider/spring/api/DBRider.java#L21

TestContextSpringRiderTestContextに置き換えているようですね。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/src/main/java/com/github/database/rider/spring/DBRiderTestExecutionListener.java

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/src/main/java/com/github/database/rider/spring/SpringRiderTestContext.java

SpringRiderTestContextがなにをしているかというと、DataSetExecutorImplのセットアップの際にSpring FrameworkDataSource定義を
使うようにしているみたいです。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/src/main/java/com/github/database/rider/spring/SpringRiderTestContext.java#L30-L41

ちなみに、ちょっとまぎらわわしいのですが、@DBRiderというアノテーションは全部で3つあります。

rider-springのもの。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-spring/src/main/java/com/github/database/rider/spring/api/DBRider.java

rider-junit5のもの。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-junit5/src/main/java/com/github/database/rider/junit5/api/DBRider.java

rider-cdiのもの。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-cdi/src/main/java/com/github/database/rider/cdi/api/DBRider.java

README.mdで書かれた@DBRiderアノテーションは、rider-junit5のものを指しています。

This module is designed to work with JUnit4 and SpringRunner, for JUnit5 please use @DBRider annotation from JUnit5 module, see an example here.

参照先のサンプルで使われているアノテーションが、com.github.database.rider.junit5.api.DBRiderだからです。

〜省略〜

import com.github.database.rider.junit5.api.DBRider;
import com.github.database.rider.springboot.model.user.User;
import com.github.database.rider.springboot.model.user.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

/**
 * Created by pestano on 13/09/16.
 */
@DBRider
@SpringBootTest
@DBUnit(cacheConnection = false, leakHunter = true)
public class SpringBootDBUnitTest {

となると、Database RiderとSpring Frameworkを組み合わせる場合は、以下の選択肢から選ぶことになるわけですね。

こう書くと、rider-junit5の場合はSpring FrameworkDataSource定義を見てくれないのでは?と思うのですが、それはrider-junit5
行ってくれるようです。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-junit5/src/main/java/com/github/database/rider/junit5/jdbc/ConnectionManager.java#L25-L32

Spring FrameworkとMicronautに関しては、rider-junit5内でハンドリングしてくれます。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-junit5/src/main/java/com/github/database/rider/junit5/integration/Spring.java

https://github.com/database-rider/database-rider/blob/1.35.0/rider-junit5/src/main/java/com/github/database/rider/junit5/integration/Micronaut.java

というわけで、Spring FrameworkJUnit 5を使う場合は、rider-junit5のみを使えば良さそうです。

追記)
と思いましたが、以下のエントリーで少し補足があります。

Database RiderをSpring Framework(Spring Boot)と合わせて使う時に、Rider JUnit 5とRider Springのどちらを使うか? - CLOVER🍀

なお、Quarkusについてはrider-cdiを使ってくれ、ということみたいです。

Database Rider / Quarkus

お題

話を戻して、今回はこちらのエントリーで書いた内容をSpring Framework(Spring Boot)と組み合わせてやってみたいと思います。

Database Riderを試してみる - CLOVER🍀

お題の内容は、以下のように見直します。

  • Database RiderとSpring Framework(Spring Boot)を使ったテストを書く
  • ファイルで用意するデータセットには、YAMLを使用する
  • データベースアクセスにはSpring JDBCを使用する
  • テスティングフレームワークには、JUnit 5を使用する
  • データベースにはMySQL 8.0を使用する

テーブル定義は以下とします。書籍がお題ですね。

create table book(
  isbn varchar(14),
  title varchar(100),
  price int,
  publish_date date,
  category varchar(20),
  primary key(isbn)
);

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu120.04)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.5, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-135-generic", arch: "amd64", family: "unix"

MySQLを使い、172.17.0.2で動作しているものとします。

$ mysql --version
mysql  Ver 8.0.31 for Linux on x86_64 (MySQL Community Server - GPL)


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.31    |
+-----------+
1 row in set (0.02 sec)

データベースはpractice、アカウントはkazuhirapasswordで作成しているものとします。

Spring Bootプロジェクトを作成する

では、Spring Bootプロジェクトを作成します。Spring Bootは3.0を使い、依存関係にはjdbcmysqlを含めておきます。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=3.0.1 \
  -d javaVersion=17 \
  -d type=maven-project \
  -d name=database-rider-spring-example \
  -d groupId=org.littlewings \
  -d artifactId=database-rider-spring-example \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.dbrider \
  -d dependencies=jdbc,mysql \
  -d baseDir=database-rider-spring-example | tar zxvf -

プロジェクト内へ移動。

$ cd database-rider-spring-example

この時点でのMaven依存関係など。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-jdbc</artifactId>
                </dependency>

                <dependency>
                        <groupId>com.mysql</groupId>
                        <artifactId>mysql-connector-j</artifactId>
                        <scope>runtime</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

生成されたソースコードは削除しておきます。

$ rm src/main/java/org/littlewings/spring/dbrider/DatabaseRiderSpringExampleApplication.java src/test/java/org/littlewings/spring/dbrider/DatabaseRiderSpringExampleApplicationTests.java

とりあえず、簡単なサンプルアプリケーションを書いておきましょう。

エンティティ的なもの。

src/main/java/org/littlewings/spring/dbrider/Book.java

package org.littlewings.spring.dbrider;

import java.time.LocalDate;

public class Book {
    String isbn;
    String title;
    Integer price;
    LocalDate publishDate;
    String category;

    // getter/setterは省略
}

動作確認用途のコンポーネント

src/main/java/org/littlewings/spring/dbrider/Runner.java

package org.littlewings.spring.dbrider;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class Runner implements ApplicationRunner {
    Logger logger = LoggerFactory.getLogger(Runner.class);

    NamedParameterJdbcTemplate jdbcTemplate;

    public Runner(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<Book> books =
                jdbcTemplate.query("""
                        select
                          isbn, title, price, publish_date, category
                        from
                          book
                        order by
                          price desc
                        """, BeanPropertyRowMapper.newInstance(Book.class));

        logger.info("found books = {}", books.size());
        books.forEach(
                book ->
                        logger.info(
                                "Book[{}]: title = {}, price = {}, publishDate = {}, category = {}",
                                book.getIsbn(),
                                book.getTitle(),
                                book.getPrice(),
                                book.getPublishDate(),
                                book.getCategory()
                        )
        );
    }
}

mainメソッドを持ったクラス。

src/main/java/org/littlewings/spring/dbrider/App.java

package org.littlewings.spring.dbrider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }
}

設定ファイル。

src/main/resources/application.properties

spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
spring.datasource.username=kazuhira
spring.datasource.password=password

これくらいで準備は完了です。

Database Riderを使ってテストを書く

では、ここにDatabase Riderを組み込み、テストを書いていきましょう。

まずは依存関係にrider-junit5を追加します。

     <dependency>
            <groupId>com.github.database-rider</groupId>
            <artifactId>rider-junit5</artifactId>
            <version>1.35.0</version>
            <scope>test</scope>
        </dependency>

テストコードの雛形は、こちら。

src/test/java/org/littlewings/spring/dbrider/DatabaseRiderSpringGettingStartedTest.java

package org.littlewings.spring.dbrider;

import java.time.LocalDate;
import java.util.List;

import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.ExpectedDataSet;
import com.github.database.rider.junit5.api.DBRider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.jdbc.JdbcTestUtils;

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

@SpringBootTest
@DBRider
public class DatabaseRiderSpringGettingStartedTest {
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    // ここに、テストを書く!!
}

まず、ポイントは@DBRiderアノテーションをつけていることですね。

@SpringBootTest
@DBRider
public class DatabaseRiderSpringGettingStartedTest {

Database Riderの設定は、dbunit.ymlで指定しました。

src/test/resources/dbunit.yml

properties:
  caseSensitiveTableNames: true

Database Rider Documentation / Configuration / DBUnit configuration

テーブル名の大文字・小文字の区別だけ無効化しています。

connectionConfigのようなデータベースへの接続情報は、Spring Bootで設定したものをrider-junit5が検出するので不要です。

あとはテストを書いていくだけです。

    @Test
    @DataSet("dataset/books.yml")
    public void gettingStarted() {
        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate.getJdbcTemplate(), "book")).isEqualTo(5);

        List<Book> books =
                jdbcTemplate.query("""
                        select
                          isbn, title, price, publish_date, category
                        from
                          book
                        order by
                          price desc
                        """, BeanPropertyRowMapper.newInstance(Book.class));

        assertThat(books).hasSize(5);

        assertThat(books.get(0).getTitle()).isEqualTo("Effective Java 第3版");
        assertThat(books.get(1).getTitle()).isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応");
        assertThat(books.get(2).getTitle()).isEqualTo("詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド");
        assertThat(books.get(3).getTitle()).isEqualTo("Java本格入門 〜モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで");
        assertThat(books.get(4).getTitle()).isEqualTo("新世代Javaプログラミングガイド[Java SE 10/11/12/13と言語拡張プロジェクト]");
    }

@DataSetアノテーションで指定したYAMLファイルは

    @Test
    @DataSet("dataset/books.yml")
    public void gettingStarted() {

こんな内容です。

src/test/resources/dataset/books.yml

book:
  - isbn: "978-4295008477"
    title: "新世代Javaプログラミングガイド[Java SE 10/11/12/13と言語拡張プロジェクト]"
    price: 2860
    publish_date: "2020-03-13"
    category: "java"
  - isbn: "978-4621303252"
    title: "Effective Java 第3版"
    price: 4400
    publish_date: "2018-10-30"
    category: "java"
  - isbn: "978-4774189093"
    title: "Java本格入門 〜モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで"
    price: 3278
    publish_date: "2017-04-18"
    category: "java"
  - isbn: "978-4798161488"
    title: "MySQL徹底入門 第4版 MySQL 8.0対応"
    price: 4180
    publish_date: "2020-07-06"
    category: "mysql"
  - isbn: "978-4798147406"
    title: "詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド"
    price: 3960
    publish_date: "2016-08-26"
    category: "mysql"

これで、対象のテスト実行時にYAMLファイルに書かれたデータ(データセット)が登録されます。

Database Rider Documentation / Configuration / DataSet configuration

今回のテストは、登録されたデータの確認を行っているだけです。

        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate.getJdbcTemplate(), "book")).isEqualTo(5);

        List<Book> books =
                jdbcTemplate.query("""
                        select
                          isbn, title, price, publish_date, category
                        from
                          book
                        order by
                          price desc
                        """, BeanPropertyRowMapper.newInstance(Book.class));

        assertThat(books).hasSize(5);

        assertThat(books.get(0).getTitle()).isEqualTo("Effective Java 第3版");
        assertThat(books.get(1).getTitle()).isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応");
        assertThat(books.get(2).getTitle()).isEqualTo("詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド");
        assertThat(books.get(3).getTitle()).isEqualTo("Java本格入門 〜モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで");
        assertThat(books.get(4).getTitle()).isEqualTo("新世代Javaプログラミングガイド[Java SE 10/11/12/13と言語拡張プロジェクト]");

ところで、このテストはYAMLファイル内のデータを登録するにも関わらず何回動かしても大丈夫なのですが、これはデフォルトの挙動が
指定されたデータセット内のデータを1度削除してからinsertするからですね。

具体的には、@DataSetアノテーションstrategy属性のデフォルト値がCLEAN_INSERTだからです。

DataSet seed strategy. Possible values are: CLEAN_INSERT, INSERT, REFRESH and UPDATE.

CLEAN_INSERT, meaning that DBUnit will clean and then insert data in tables present in provided dataset.

Database Rider Documentation / Configuration / DataSet configuration

次に、@ExpectedDataSetアノテーションを使ってみます。

Database Rider Documentation / DataSet assertion

まずは@DataSet@ExpectedDataSetのみを指定。データは主キーでソートするようにしています。

    @Test
    @DataSet("dataset/books.yml")
    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn")
    public void withExpected() {
    }

アサーション用のYAMLはこちら。

src/test/resources/dataset/expectedBooks.yml

book:
  - isbn: "978-4295008477"
    title: "新世代Javaプログラミングガイド[Java SE 10/11/12/13と言語拡張プロジェクト]"
    price: 2860
    publish_date: "2020-03-13"
    category: "java"
  - isbn: "978-4621303252"
    title: "Effective Java 第3版"
    price: 4400
    publish_date: "2018-10-30"
    category: "java"
  - isbn: "978-4774189093"
    title: "Java本格入門 〜モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで"
    price: 3278
    publish_date: "2017-04-18"
    category: "java"
  - isbn: "978-4798161488"
    title: "MySQL徹底入門 第4版 MySQL 8.0対応"
    price: 4180
    publish_date: "2020-07-06"
    category: "mysql"
  - isbn: "978-4798147406"
    title: "詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド"
    price: 3960
    publish_date: "2016-08-26"
    category: "mysql"
  - isbn: "978-4297126858"
    title: "プロになるJava―仕事で必要なプログラミングの知識がゼロから身につく最高の指南書"
    price: 3278
    publish_date: "2022-03-19"
    category: "java"

@DataSetアノテーションで指定したファイルの内容に1件データを追加しているので、このテストは失敗します。

[ERROR] withExpected  Time elapsed: 0.193 s  <<< FAILURE!
org.dbunit.assertion.DbComparisonFailure: row count (table=book) expected:<6> but was:<5>

というわけで、今度はテスト内でデータを登録するようにしてみましょう。

    @Test
    @DataSet("dataset/books.yml")
    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn")
    public void addDataAndAssertion() {
        jdbcTemplate
                .update("""
                                insert into
                                  book(isbn, title, price, publish_date, category)
                                values(:isbn, :title, :price, :publishDate, :category)
                                """,
                        new MapSqlParameterSource()
                                .addValue("isbn", "978-4297126858")
                                .addValue("title", "プロになるJava―仕事で必要なプログラミングの知識がゼロから身につく最高の指南書")
                                .addValue("price", 3278)
                                .addValue("publishDate", LocalDate.of(2022, 3, 19))
                                .addValue("category", "java")
                );

        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate.getJdbcTemplate(), "book")).isEqualTo(6);
    }

こうすると、@ExpectedDataSetアノテーションで指定したデータセットと内容が合うのでテストがパスします。

@Transactionalアノテーションと組み合わせた場合は?

ところで、Spring Frameworkを使ったテストで@Transactionalアノテーションを使うと、テスト完了時にはデフォルトで
ロールバックするのでした。

By default, test transactions will be automatically rolled back after completion of the test;

Testing / Spring TestContext Framework / Transaction Management / Transaction Rollback and Commit Behavior

これは、@ExpectedDataSetアノテーションと組み合わせるとどうなるのでしょう?

こんなテストコードを用意して確認。

src/test/java/org/littlewings/spring/dbrider/DatabaseRiderSpringTransactionalTest.java
package org.littlewings.spring.dbrider;

import java.time.LocalDate;

import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.ExpectedDataSet;
import com.github.database.rider.junit5.api.DBRider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.annotation.Transactional;

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

@SpringBootTest
@DBRider
public class DatabaseRiderSpringTransactionalTest {
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    @Transactional
    @Test
    @DataSet("dataset/books.yml")
    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn")
    public void addDataAndAssertion() {
        jdbcTemplate
                .update("""
                                insert into
                                  book(isbn, title, price, publish_date, category)
                                values(:isbn, :title, :price, :publishDate, :category)
                                """,
                        new MapSqlParameterSource()
                                .addValue("isbn", "978-4297126858")
                                .addValue("title", "プロになるJava―仕事で必要なプログラミングの知識がゼロから身につく最高の指南書")
                                .addValue("price", 3278)
                                .addValue("publishDate", LocalDate.of(2022, 3, 19))
                                .addValue("category", "java")
                );

        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate.getJdbcTemplate(), "book")).isEqualTo(6);
    }
}

すると、こちらのアサーションは良いのですが

        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate.getJdbcTemplate(), "book")).isEqualTo(6);

@ExpectedDataSetアノテーションを使ったアサーションが失敗します。

    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn")
    public void addDataAndAssertion() {
org.dbunit.assertion.DbComparisonFailure: row count (table=book) expected:<6> but was:<5>

ロールバックが行われるのはDatabase Riderによるアサーションの後だったりするのですが、うまくいかないようです。

これは、使用するDataSourceこそSpring Framework(Spring Boot)側と同じなのですが、データベース接続が別々だからですね。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-junit5/src/main/java/com/github/database/rider/junit5/jdbc/ConnectionManager.java#L53

アサーションを行うDatabase Riderから、別の接続のトランザクションの内容が見えないのです。

ですので、たとえばapplication.propertiesに以下のようにデータソースのプールサイズを1にしたりすると

spring.datasource.hikari.maximum-pool-size=1

Database Rider分のデータベース接続が取得できなくなります。

java.lang.RuntimeException: Could not get connection from DataSource.

    at com.github.database.rider.junit5.jdbc.ConnectionManager.getConnectionHolder(ConnectionManager.java:56)
    at com.github.database.rider.junit5.integration.Spring.getConnectionFromSpringContext(Spring.java:30)
    at com.github.database.rider.junit5.jdbc.ConnectionManager.getTestConnection(ConnectionManager.java:27)
    at com.github.database.rider.junit5.DBUnitExtension.createDBUnitTestContext(DBUnitExtension.java:100)
    at com.github.database.rider.junit5.DBUnitExtension.lambda$getTestContext$0(DBUnitExtension.java:95)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$4(ExtensionValuesStore.java:86)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.computeValue(ExtensionValuesStore.java:223)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:211)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.evaluate(ExtensionValuesStore.java:191)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.access$100(ExtensionValuesStore.java:171)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore.getOrComputeIfAbsent(ExtensionValuesStore.java:89)
    at org.junit.jupiter.engine.execution.ExtensionValuesStore.getOrComputeIfAbsent(ExtensionValuesStore.java:93)
    at org.junit.jupiter.engine.execution.NamespaceAwareStore.getOrComputeIfAbsent(NamespaceAwareStore.java:61)
    at com.github.database.rider.junit5.DBUnitExtension.getTestContext(DBUnitExtension.java:95)
    at com.github.database.rider.junit5.DBUnitExtension.beforeTestExecution(DBUnitExtension.java:53)

    〜省略〜

ちなみに、Database Riderの@DataSetアノテーションにもtransactionalという属性があるようです。

@Test
@DataSet(cleanBefore = true, transactional = true, executorId = "TransactionIt")
@ExpectedDataSet(value = "yml/expectedUsersRegex.yml")
@DBUnit(cacheConnection = true)
public void shouldManageTransactionAutomatically() {

Database Rider Documentation / DataSet assertion / Assertion using automatic transaction

こちらを使うとテストの前にトランザクションを開始し、テストの実行後かつ期待するデータセットとの比較の前にトランザクション
コミットする挙動になるようです。

Transactional attribute will make Database Rider start a transaction before test and commit the transaction after test execution but before expected dataset comparison.

どちらにしても、挙動として覚えておいた方が良さそうですね。

なお、この挙動はrider-springを使用すると異なる動きになります。

Database RiderをSpring Framework(Spring Boot)と合わせて使う時に、Rider JUnit 5とRider Springのどちらを使うか? - CLOVER🍀

まとめ

Database RiderをSpring Framework(Spring Boot)と組み合わせて使ってみました。

rider-springがあるので、依存関係や使うAPIをどうするのかやや迷ったのですが、最終的には読み解けたのでよかったかなと。

Spring Framework側とデータソース定義は同じものを使うものの、接続自体が別になるのは覚えておいた方が良さそうですね。