これは、なにをしたくて書いたもの?
AssertJのモジュールのひとつである、データベースのデータに対するアサーションができるAssertJ-DBを試してみようかなということで。
AssertJ-DB
AssertJ-DBのドキュメントは、AssertJのドキュメントの中に含まれています。
AssertJ - fluent assertions java library
こちらですね。
AssertJ - fluent assertions java library / AssertJ DB
GitHub - assertj/assertj-db: Assertions for database
独立したリポジトリーになっています。現在のバージョンは3.0.0です。
ざっくり言うと、以下のような感じです。
サンプルコードはこちらにあります。
たぶん、実際に使うのはChanges
だと思うのですが、慣れるためにTable
、Request
の順に見ていった方がよい気がします。
説明を続けるよりも実際に見ていった方が早いかなと思うので、試していってみましょう。
環境
今回の環境はこちら。
$ java --version openjdk 21.0.5 2024-10-15 OpenJDK Runtime Environment (build 21.0.5+11-Ubuntu-1ubuntu124.04) OpenJDK 64-Bit Server VM (build 21.0.5+11-Ubuntu-1ubuntu124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.5, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-49-generic", arch: "amd64", family: "unix"
データベースにはMySQLを使います。
MySQL localhost:3306 ssl practice SQL > select version(); +-----------+ | version() | +-----------+ | 8.4.3 | +-----------+ 1 row in set (0.0006 sec)
MySQLには172.17.0.2でアクセスできるものとします。
お題
書籍とそのカテゴリーをお題に、AssertJ-DBを使ってアサーションをしてみたいと思います。
create table if not exists category( id integer, name varchar(100), primary key(id) ); create table if not exists book( isbn varchar(14), title varchar(100), price int, publish_date date, category_id integer, primary key(isbn), foreign key(category_id) references category(id) );
準備
Maven依存関係など。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>9.1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.seasar.doma</groupId> <artifactId>doma-core</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.11.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-db</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <annotationProcessorPaths> <path> <groupId>org.seasar.doma</groupId> <artifactId>doma-processor</artifactId> <version>3.1.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
データベースアクセスにはDomaを使うことにします。
AssertJ-DBを使うのに必要な依存関係はこちらだけですね。
<dependency> <groupId>org.assertj</groupId> <artifactId>assertj-db</artifactId> <version>3.0.0</version> <scope>test</scope> </dependency>
AssertJ-DBはAssertJ-Coreに依存しています。
テストコード以外のものも用意していきましょう。
エンティティクラスとDao。
src/main/java/org/littlewings/assertjdb/Category.java
package org.littlewings.assertjdb; import org.seasar.doma.Entity; import org.seasar.doma.Id; @Entity public record Category( @Id Integer id, String name ) { }
src/main/java/org/littlewings/assertjdb/CategoryDao.java
package org.littlewings.assertjdb; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Script; import org.seasar.doma.Sql; import org.seasar.doma.Update; import org.seasar.doma.jdbc.Result; @Dao public interface CategoryDao { @Sql(""" create table if not exists category( id integer, name varchar(100), primary key(id) ) """) @Script void createTableIfNotExists(); @Insert Result<Category> insert(Category category); @Update Result<Category> update(Category category); @Delete Result<Category> delete(Category category); @Sql("delete from category") @Delete int deleteAll(); }
src/main/java/org/littlewings/assertjdb/Book.java
package org.littlewings.assertjdb; import java.time.LocalDate; import org.seasar.doma.Entity; import org.seasar.doma.Id; @Entity public record Book( @Id String isbn, String title, Integer price, LocalDate publishDate, Integer categoryId ) { }
src/main/java/org/littlewings/assertjdb/BookDao.java
package org.littlewings.assertjdb; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Script; import org.seasar.doma.Sql; import org.seasar.doma.Update; import org.seasar.doma.jdbc.Result; @Dao public interface BookDao { @Sql(""" create table if not exists book( isbn varchar(14), title varchar(100), price int, publish_date date, category_id integer, primary key(isbn), foreign key(category_id) references category(id) ) """) @Script void createTableIfNotExists(); @Insert Result<Book> insert(Book book); @Update Result<Book> update(Book book); @Delete Result<Book> delete(Book book); @Sql("delete from book") @Delete int deleteAll(); }
簡単のため、DDLはDaoのメソッドのひとつとして用意することにしました。
Domaの設定。
src/main/java/org/littlewings/assertjdb/DomaConfig.java
package org.littlewings.assertjdb; import javax.sql.DataSource; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.Naming; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.MysqlDialect; import org.seasar.doma.jdbc.tx.LocalTransactionDataSource; import org.seasar.doma.jdbc.tx.LocalTransactionManager; import org.seasar.doma.jdbc.tx.TransactionManager; public class DomaConfig implements Config { private static final DomaConfig CONFIG = new DomaConfig(); private Dialect dialect; private LocalTransactionDataSource dataSource; private TransactionManager transactionManager; private DomaConfig() { dialect = new MysqlDialect(); dataSource = new LocalTransactionDataSource( "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin", "kazuhira", "password" ); transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger())); } @Override public DataSource getDataSource() { return dataSource; } @Override public Dialect getDialect() { return dialect; } @Override public TransactionManager getTransactionManager() { return transactionManager; } @Override public Naming getNaming() { return Naming.SNAKE_LOWER_CASE; } public static DomaConfig singleton() { return CONFIG; } }
テストコードの雛形。
src/test/java/org/littlewings/assertjdb/AssertJDbTest.java
package org.littlewings.assertjdb; import java.time.LocalDate; import java.util.List; import java.util.Map; import org.assertj.db.type.AssertDbConnection; import org.assertj.db.type.AssertDbConnectionFactory; import org.assertj.db.type.Changes; import org.assertj.db.type.DateValue; import org.assertj.db.type.Request; import org.assertj.db.type.Table; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.db.api.Assertions.assertThat; import static org.assertj.db.output.Outputs.output; public class AssertJDbTest { @BeforeAll static void setUpAll() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { categoryDao.createTableIfNotExists(); bookDao.createTableIfNotExists(); }); } @BeforeEach void setUp() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { bookDao.deleteAll(); categoryDao.deleteAll(); }); } // ここにテストを書く! }
テスト実行前にテーブルを作成し、テストの都度データをすべて削除するようにしています。
準備はここまでにして、AssertJ-DBを使っていってみましょう。
Tableを使う
まずはTableを使ってみましょう。
Getting Started的な
作成したテストコードはこちら。
@Test void gettingStarted() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Map<String, Category> categories = Map.of( "Java", new Category(1, "Java"), "MySQL", new Category(2, "MySQL") ); List<Book> books = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), categories.get("Java").id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), categories.get("Java").id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), categories.get("Java").id()), new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), categories.get("MySQL").id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), categories.get("MySQL").id()) ); categories.values().forEach(categoryDao::insert); books.forEach(bookDao::insert); }); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of( "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin", "kazuhira", "password" ) .create(); //AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Table categoryTable = assertDbConnection.table("category").build(); assertThat(categoryTable) .hasNumberOfRows(2) .column("id") .value().isEqualTo(1) .value().isEqualTo(2) .column("name") .value().isEqualTo("Java") .value().isEqualTo("MySQL"); Table bookTable = assertDbConnection.table("book").build(); assertThat(bookTable) .hasNumberOfRows(5) .column("title") .value().isEqualTo("MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ") .value().isEqualTo("Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~") .value().isEqualTo("Effective Java 第3版") .value().isEqualTo("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで") .value().isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応") .column("publish_date") .hasValues( // この順番で DateValue.from(LocalDate.of(2024, 5, 22)), DateValue.from(LocalDate.of(2024, 10, 3)), DateValue.from(LocalDate.of(2018, 10, 30)), DateValue.from(LocalDate.of(2017, 4, 18)), DateValue.from(LocalDate.of(2020, 7, 6)) ) .column("category_id") .hasValues(2, 1, 1, 1, 2); }
最初の部分はデータを登録しているだけなので端折ります。
AssertJ-DBを使う時に最初に行うのは、AssertDbConnection
の作成です。これはJDBCの接続パラメーターかDataSource
のどちらかを
指定して作成することができます。
AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of( "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin", "kazuhira", "password" ) .create(); //AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create();
AssertJ - fluent assertions java library / AssertJ DB / Concepts / Connection to the database
今回はJDBCの接続パラメーターを与えていますが、実際に使う時にはDataSource
を指定することが多いような気がします。
以降はDataSource
を指定することにします。
なお、データベース内のメタデータを取得するようなのですが、テーブル数が多い場合などは1度だけ取得するように設定した方が
よさそうです。
次にAssertDbConnection
からTable
を取得します。
Table categoryTable = assertDbConnection.table("category").build(); Table bookTable = assertDbConnection.table("book").build();
Table
は文字通りデータベースのテーブルを表したものです。
AssertJ - fluent assertions java library / AssertJ DB / Concepts / Elements of the database / Table
アサーションの例。
最初にカラム名を指定すると、カラム → 行の順でアサーションしていくことになるようです。hasNumberOfRows
はテーブルの行数ですね。
assertThat(categoryTable) .hasNumberOfRows(2) .column("id") .value().isEqualTo(1) .value().isEqualTo(2) .column("name") .value().isEqualTo("Java") .value().isEqualTo("MySQL");
カラムひとつずつではなく、hasValues
を使うとその行に含まれるすべてのカラムをまとめてアサーションすることもできます。
assertThat(bookTable) .hasNumberOfRows(5) .column("title") .value().isEqualTo("MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ") .value().isEqualTo("Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~") .value().isEqualTo("Effective Java 第3版") .value().isEqualTo("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで") .value().isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応") .column("publish_date") .hasValues( // この順番で DateValue.from(LocalDate.of(2024, 5, 22)), DateValue.from(LocalDate.of(2024, 10, 3)), DateValue.from(LocalDate.of(2018, 10, 30)), DateValue.from(LocalDate.of(2017, 4, 18)), DateValue.from(LocalDate.of(2020, 7, 6)) ) .column("category_id") .hasValues(2, 1, 1, 1, 2);
データは主キーの昇順でソートされているようです。
アサーションについてはこちら。
AssertJ - fluent assertions java library / AssertJ DB / Features highlight / Navigation / Assertions
ちなみに、日付型はLocalDate
で扱えるはずなのですがDateValue
に変換しているのは使っているMySQLの都合だと思います…。
行でナビゲーションする
個人的にはカラム → 行の順はちょっとわかりにくい気がするので、行 → カラムで見れるようにしてみましょう。
@Test void orderByAndRowNavigation() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Map<String, Category> categories = Map.of( "Java", new Category(1, "Java"), "MySQL", new Category(2, "MySQL") ); List<Book> books = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), categories.get("Java").id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), categories.get("Java").id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), categories.get("Java").id()), new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), categories.get("MySQL").id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), categories.get("MySQL").id()) ); categories.values().forEach(categoryDao::insert); books.forEach(bookDao::insert); }); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Table categoryTable = assertDbConnection.table("category").columnsToOrder(new Table.Order[]{Table.Order.asc("id")}).build(); assertThat(categoryTable) .hasNumberOfRows(2) .row() .column("id") .value().isEqualTo(1) .column("name") .value().isEqualTo("Java") .row() .column("id") .value().isEqualTo(2) .column("name") .value().isEqualTo("MySQL"); Table bookTable = assertDbConnection.table("book").columnsToOrder(new Table.Order[]{Table.Order.asc("price")}).build(); assertThat(bookTable) .hasNumberOfRows(5) .row() .hasValues( // 取得した全カラムを並べる "978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, DateValue.from(LocalDate.of(2024, 5, 22)), 2 ) .row() .hasValues( "978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, DateValue.from(LocalDate.of(2017, 4, 18)), 1 ) .row() .hasValues( "978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, DateValue.from(LocalDate.of(2024, 10, 3)), 1 ) .row() .hasValues( "978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, DateValue.from(LocalDate.of(2020, 7, 6)), 2 ) .row() .hasValues( "978-4621303252", "Effective Java 第3版", 4400, DateValue.from(LocalDate.of(2018, 10, 30)), 1 ); }
AssertDbConnection
の取得はDataSource
からにしました。
AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create();
また、テーブルのソート順も指定するようにしました。
Table categoryTable = assertDbConnection.table("category").columnsToOrder(new Table.Order[]{Table.Order.asc("id")}).build(); Table bookTable = assertDbConnection.table("book").columnsToOrder(new Table.Order[]{Table.Order.asc("price")}).build();
行でナビゲーションするには、row
を使います。行ごとに、カラムを指定してアサーション。
assertThat(categoryTable) .hasNumberOfRows(2) .row() .column("id") .value().isEqualTo(1) .column("name") .value().isEqualTo("Java") .row() .column("id") .value().isEqualTo(2) .column("name") .value().isEqualTo("MySQL");
row
にインデックスを指定することで、指定の行へ移動することもできるようです。
行の内容をまとめてアサーション。
assertThat(bookTable) .hasNumberOfRows(5) .row() .hasValues( // 取得した全カラムを並べる "978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, DateValue.from(LocalDate.of(2024, 5, 22)), 2 ) .row() .hasValues( "978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, DateValue.from(LocalDate.of(2017, 4, 18)), 1 ) .row() .hasValues( "978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, DateValue.from(LocalDate.of(2024, 10, 3)), 1 ) .row() .hasValues( "978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, DateValue.from(LocalDate.of(2020, 7, 6)), 2 ) .row() .hasValues( "978-4621303252", "Effective Java 第3版", 4400, DateValue.from(LocalDate.of(2018, 10, 30)), 1 );
その他のTable
に対するナビゲーションについてはこちらを参照。
Tableの内容を出力する
Table
の内容を出力するには、output
を使います。
@Test void tableOutput() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Map<String, Category> categories = Map.of( "Java", new Category(1, "Java"), "MySQL", new Category(2, "MySQL") ); List<Book> books = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), categories.get("Java").id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), categories.get("Java").id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), categories.get("Java").id()), new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), categories.get("MySQL").id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), categories.get("MySQL").id()) ); categories.values().forEach(categoryDao::insert); books.forEach(bookDao::insert); }); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Table categoryTable = assertDbConnection.table("category").columnsToOrder(new Table.Order[]{Table.Order.asc("id")}).build(); Table bookTable = assertDbConnection.table("book").columnsToOrder(new Table.Order[]{Table.Order.asc("price")}).build(); output(categoryTable).toConsole(); output(bookTable).toConsole(); }
結果はこんな感じになります。
[category table] |-----------|---------|-----------|-----------| | | | * | | | | PRIMARY | ID | NAME | | | KEY | (NUMBER) | (TEXT) | | | | Index : 0 | Index : 1 | |-----------|---------|-----------|-----------| | Index : 0 | 1 | 1 | Java | | Index : 1 | 2 | 2 | MySQL | |-----------|---------|-----------|-----------| [book table] |-----------|----------------|----------------|----------------------------------------------------|-----------|--------------|-------------| | | | * | | | | | | | PRIMARY | ISBN | TITLE | PRICE | PUBLISH_DATE | CATEGORY_ID | | | KEY | (TEXT) | (TEXT) | (NUMBER) | (DATE) | (NUMBER) | | | | Index : 0 | Index : 1 | Index : 2 | Index : 3 | Index : 4 | |-----------|----------------|----------------|----------------------------------------------------|-----------|--------------|-------------| | Index : 0 | 978-4297141844 | 978-4297141844 | MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ | 3080 | 2024-05-22 | 2 | | Index : 1 | 978-4774189093 | 978-4774189093 | Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで | 3278 | 2017-04-18 | 1 | | Index : 2 | 978-4297144357 | 978-4297144357 | Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~ | 3520 | 2024-10-03 | 1 | | Index : 3 | 978-4798161488 | 978-4798161488 | MySQL徹底入門 第4版 MySQL 8.0対応 | 4180 | 2020-07-06 | 2 | | Index : 4 | 978-4621303252 | 978-4621303252 | Effective Java 第3版 | 4400 | 2018-10-30 | 1 | |-----------|----------------|----------------|----------------------------------------------------|-----------|--------------|-------------|
AssertJ - fluent assertions java library / AssertJ DB / Concepts / Output
現在の状態がよくわかって便利なのですが、これが表示できるのはTable
のみのようです。
この後に出てくるRequest
とChanges
でもメソッド自体は呼べるのですが、なにも表示されませんでした…。
カラムを除外する
Table
を取得する際に、アサーション対象のカラムを除外できます。
@Test void columnsExclude() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Map<String, Category> categories = Map.of( "Java", new Category(1, "Java"), "MySQL", new Category(2, "MySQL") ); List<Book> books = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), categories.get("Java").id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), categories.get("Java").id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), categories.get("Java").id()), new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), categories.get("MySQL").id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), categories.get("MySQL").id()) ); categories.values().forEach(categoryDao::insert); books.forEach(bookDao::insert); }); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Table bookTable = assertDbConnection .table("book") .columnsToOrder(new Table.Order[]{Table.Order.asc("price")}) .columnsToExclude(new String[]{"isbn", "publish_date", "category_id"}) .build(); assertThat(bookTable) .hasNumberOfRows(5) .row() .hasValues( "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080 ) .row() .hasValues( "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278 ) .row() .hasValues( "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520 ) .row() .hasValues( "MySQL徹底入門 第4版 MySQL 8.0対応", 4180 ) .row() .hasValues( "Effective Java 第3版", 4400 ); }
この部分ですね。
Table bookTable = assertDbConnection .table("book") .columnsToOrder(new Table.Order[]{Table.Order.asc("price")}) .columnsToExclude(new String[]{"isbn", "publish_date", "category_id"}) .build();
hasValues
では取得したすべてのカラムの値を列挙する必要がありますが、除外したカラムについては対象外になります。
assertThat(bookTable) .hasNumberOfRows(5) .row() .hasValues( "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080 )
AssertJ - fluent assertions java library / AssertJ DB / Concepts / Elements of the database / Table
Request
Request
は、SQLを使ってデータを取得した結果を表したものです。
@Test void request() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Map<String, Category> categories = Map.of( "Java", new Category(1, "Java"), "MySQL", new Category(2, "MySQL") ); List<Book> books = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), categories.get("Java").id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), categories.get("Java").id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), categories.get("Java").id()), new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), categories.get("MySQL").id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), categories.get("MySQL").id()) ); categories.values().forEach(categoryDao::insert); books.forEach(bookDao::insert); }); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Request categoryRequest = assertDbConnection .request("select id, name from category where id = ? order by id") .parameters(1) .build(); assertThat(categoryRequest) .hasNumberOfRows(1) .row() .column("id") .value().isEqualTo(1) .column("name") .value().isEqualTo("Java"); Request bookRequest = assertDbConnection .request("select isbn, title, price, publish_date, category_id from book where price > ? and category_id = ? order by price asc") .parameters(4000, 1) .build(); assertThat(bookRequest) .hasNumberOfRows(1) .row() .hasValues( "978-4621303252", "Effective Java 第3版", 4400, DateValue.from(LocalDate.of(2018, 10, 30)), 1 ); }
こんな感じで、データの取得方法がSQLになります。バインドパラメーターも使えます。
Request categoryRequest = assertDbConnection .request("select id, name from category where id = ? order by id") .parameters(1) .build(); Request bookRequest = assertDbConnection .request("select isbn, title, price, publish_date, category_id from book where price > ? and category_id = ? order by price asc") .parameters(4000, 1) .build();
それ以外の使い方は、Table
と変わりません。
Changes
Changes
は開始時点と終了時点の2点の差をとったものです。
Changes
およびその中にある変更のひとつひとつであるChange
がどう表現されるかがこちらに書いてあります。
- AssertJ - fluent assertions java library / AssertJ DB / Concepts / Elements of the database / Changes
- AssertJ - fluent assertions java library / AssertJ DB / Concepts / Elements of the database / Change
まあ、最初は流し読みしてしまうと思うのですが、これが頭に入っていないとアサーションが書けません…。
先に例を載せましょう。
@Test void changes() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Category javaCategory = new Category(1, "Java"); List<Book> javaBooks = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), javaCategory.id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), javaCategory.id()), new Book("978-4798151120", "独習Java 新版", 3278, LocalDate.of(2019, 5, 15), javaCategory.id()) ); categoryDao.insert(javaCategory); javaBooks.forEach(bookDao::insert); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Changes changes = assertDbConnection.changes().build(); // 開始 changes.setStartPointNow(); Category mysqlCategory = new Category(2, "MySQL"); List<Book> mysqlBooks = List.of( new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), mysqlCategory.id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), mysqlCategory.id()) ); // insert categoryDao.insert(mysqlCategory); mysqlBooks.forEach(bookDao::insert); // update bookDao.update(new Book("978-4621303252", "Effective Java", 3000, LocalDate.of(2018, 10, 30), javaCategory.id())); bookDao.update(new Book("978-4774189093", "Java本格入門", 2500, LocalDate.of(2017, 4, 18), javaCategory.id())); // delete bookDao.delete(new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id())); // 終了 changes.setEndPointNow(); assertThat(changes) // 全体の変更数 .hasNumberOfChanges(6) // 種類ごとの変更数 .ofCreation().hasNumberOfChanges(3) .ofModification().hasNumberOfChanges(2) .ofDeletion().hasNumberOfChanges(1) // categoryテーブルの変更数 .onTable("category") .hasNumberOfChanges(1) // bookテーブルの変更数 .onTable("book") .hasNumberOfChanges(5); assertThat(changes) // ひとつ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4297141844") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .hasValues("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, DateValue.from(LocalDate.of(2024, 5, 22)), 2) // 2つ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4798161488") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .value("isbn").isEqualTo("978-4798161488") .value("title").isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応") .value("price").isEqualTo(4180) .value("publish_date").isEqualTo(DateValue.from(LocalDate.of(2020, 7, 6))) .value("category_id").isEqualTo(2) // 3つ目の変更(insert) .change() .isOnTable("category") .isCreation() .hasPksNames("id") .hasPksValues(2) // 開始時点の行 .rowAtStartPoint() .doesNotExist() .rowAtEndPoint() .value("id").isEqualTo(2) .value("name").isEqualTo("MySQL") // 4つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4621303252") // 変更されたカラム .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Effective Java 第3版", "Effective Java") .column("price").hasValues(4400, 3000) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Effective Java 第3版") .value("price").isEqualTo(4400) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Effective Java") .value("price").isEqualTo(3000) // 5つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4774189093") .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", "Java本格入門") .column("price").hasValues(3278, 2500) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで") .value("price").isEqualTo(3278) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Java本格入門") .value("price").isEqualTo(2500) // 6つ目の変更(delete) .change() .isOnTable("book") .isDeletion() .hasPksNames("isbn") .hasPksValues("978-4297144357") // 開始時点の行 .rowAtStartPoint() .value("isbn").isEqualTo("978-4297144357") .value("title").isEqualTo("Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~") // 終了時点の行 .rowAtEndPoint() .doesNotExist(); }); }
今回は、データの操作を2つに分けています。
まずはデータの登録。
.required(() -> { Category javaCategory = new Category(1, "Java"); List<Book> javaBooks = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), javaCategory.id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), javaCategory.id()), new Book("978-4798151120", "独習Java 新版", 3278, LocalDate.of(2019, 5, 15), javaCategory.id()) ); categoryDao.insert(javaCategory); javaBooks.forEach(bookDao::insert);
この後にChanges
を取得します。
AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Changes changes = assertDbConnection.changes().build();
Changes
は取得しただけではなにも起こらず、変更を記録するにはChanges#setStartPointNow
を呼び出す必要があります。
// 開始
changes.setStartPointNow();
この後、データの登録と更新、削除を行ってみます。
Category mysqlCategory = new Category(2, "MySQL"); List<Book> mysqlBooks = List.of( new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), mysqlCategory.id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), mysqlCategory.id()) ); // insert categoryDao.insert(mysqlCategory); mysqlBooks.forEach(bookDao::insert); // update bookDao.update(new Book("978-4621303252", "Effective Java", 3000, LocalDate.of(2018, 10, 30), javaCategory.id())); bookDao.update(new Book("978-4774189093", "Java本格入門", 2500, LocalDate.of(2017, 4, 18), javaCategory.id())); // delete bookDao.delete(new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id()));
そしてChanges#setEndPointNow
を呼び出します。
// 終了
changes.setEndPointNow();
ここでChanges#setStartPointNow
を呼び出してからChanges#setEndPointNow
までの間の変更が検出されることになります。
あとはアサーションです。
まずは変更全体を見てみます。
assertThat(changes) // 全体の変更数 .hasNumberOfChanges(6) // 種類ごとの変更数 .ofCreation().hasNumberOfChanges(3) .ofModification().hasNumberOfChanges(2) .ofDeletion().hasNumberOfChanges(1) // categoryテーブルの変更数 .onTable("category") .hasNumberOfChanges(1) // bookテーブルの変更数 .onTable("book") .hasNumberOfChanges(5);
6つの変更があり、insert(creation)が3つ、update(modification)が2つ、delete(deletion)がひとつです。
テーブルごとには変更の件数のみがわかりそうです。
次は変更ごとのアサーションです。
Changes
の中に含まれるChange
は、以下の順で並んでいます。
The changes are ordered :
- First by the type of the change : creation, modification and after deletion
- After if it is a change on a table by the name of the table
- To finish by the values of the primary key and if there are no primary key by the values of the row (for a modification)
つまり、こういうことです。
- insert、update、deleteで大きく分類
- 分類内ではテーブル名でソート
- 行はプライマリーキーの順でソート、プライマリーキーがない場合は行の値すべてでソート
なので最初はinsertに対するアサーションを書くことになります。
// ひとつ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4297141844") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .hasValues("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, DateValue.from(LocalDate.of(2024, 5, 22)), 2)
こういうアサーションになっています。
isOnTable
でどのテーブルの変更なのかを確認isCreation
でinsertであることを確認hasPksNames
とhasPksValues
で変更があった行のプライマリーキーのカラム名と値を確認rowAtStartPoint
でChanges
の開始時点(Changes#setStartPointNow
)では行がなかったことを確認rowAtEndPoint
でChanges
の終了時点(Changes#setEndPointNow
)での行の値を確認
このように、変更内容ひとつひとつ見ていくことになり、Table
やRequest
とアサーション時のナビゲーションの考え方がだいぶ異なります。
ここでchange
を呼び出すことで、次の変更を見ることができます。
// 2つ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4798161488") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .value("isbn").isEqualTo("978-4798161488") .value("title").isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応") .value("price").isEqualTo(4180) .value("publish_date").isEqualTo(DateValue.from(LocalDate.of(2020, 7, 6))) .value("category_id").isEqualTo(2) // 3つ目の変更(insert) .change() .isOnTable("category") .isCreation() .hasPksNames("id") .hasPksValues(2) // 開始時点の行 .rowAtStartPoint() .doesNotExist() .rowAtEndPoint() .value("id").isEqualTo(2) .value("name").isEqualTo("MySQL")
insertの変更を見終えると、次はupdateの変更に移ります。
// 4つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4621303252") // 変更されたカラム .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Effective Java 第3版", "Effective Java") .column("price").hasValues(4400, 3000) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Effective Java 第3版") .value("price").isEqualTo(4400) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Effective Java") .value("price").isEqualTo(3000)
こういうアサーションになっています。
isOnTable
でどのテーブルの変更なのかを確認isModification
でupdateであることを確認hasPksNames
とhasPksValues
で変更があった行のプライマリーキーのカラム名と値を確認hasModifiedColumns
で変更されたカラムを確認column
とhasValues
で変更前後のカラムの値を確認rowAtStartPoint
でChanges
の開始時点(Changes#setStartPointNow
)でのカラムの値を確認rowAtEndPoint
でChanges
の終了時点(Changes#setEndPointNow
)でのカラムの値を確認
column
とhasValues
で行っているアサーションと、rowAtStartPoint
とrowAtEndPoint
で行っているアサーションは意味としては同じです。
実際に使う時はどちらかでよいでしょう。
この後もひとつupdateが続きます。
// 5つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4774189093") .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", "Java本格入門") .column("price").hasValues(3278, 2500) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで") .value("price").isEqualTo(3278) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Java本格入門") .value("price").isEqualTo(2500)
最後はdeleteです。
// 6つ目の変更(delete) .change() .isOnTable("book") .isDeletion() .hasPksNames("isbn") .hasPksValues("978-4297144357") // 開始時点の行 .rowAtStartPoint() .value("isbn").isEqualTo("978-4297144357") .value("title").isEqualTo("Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~") // 終了時点の行 .rowAtEndPoint() .doesNotExist();
こういうアサーションになっています。
isOnTable
でどのテーブルの変更なのかを確認isDeletion
でdeleteであることを確認hasPksNames
とhasPksValues
で変更があった行のプライマリーキーのカラム名と値を確認hasModifiedColumns
で変更されたカラムを確認rowAtStartPoint
でChanges
の開始時点(Changes#setStartPointNow
)でのカラムの値を確認rowAtEndPoint
でChanges
の終了時点(Changes#setEndPointNow
)で行がなくなっていることを確認
今回は扱っていませんが、ofCreation
、ofCreationOnTable
といったものを使って変更の種類や指定したテーブルの変更の種類に沿った
ナビゲーションもできるようです。
最初、ナビゲーションの考え方を押さえていなくてTable
やRequest
と同じように扱おうとしてかなりてこずりました…。
Changes
を取得する対象は、特定のTable
やRequest
に絞ることもできます。
@Test void changesInclusion() { CategoryDao categoryDao = new CategoryDaoImpl(DomaConfig.singleton()); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); DomaConfig .singleton() .getTransactionManager() .required(() -> { Category javaCategory = new Category(1, "Java"); List<Book> javaBooks = List.of( new Book("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), javaCategory.id()), new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id()), new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278, LocalDate.of(2017, 4, 18), javaCategory.id()), new Book("978-4798151120", "独習Java 新版", 3278, LocalDate.of(2019, 5, 15), javaCategory.id()) ); categoryDao.insert(javaCategory); javaBooks.forEach(bookDao::insert); AssertDbConnection assertDbConnection = AssertDbConnectionFactory.of(DomaConfig.singleton().getDataSource()).create(); Table book = assertDbConnection.table("book").build(); Changes changes = assertDbConnection.changes().tables(book).build(); // 開始 changes.setStartPointNow(); Category mysqlCategory = new Category(2, "MySQL"); List<Book> mysqlBooks = List.of( new Book("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, LocalDate.of(2024, 5, 22), mysqlCategory.id()), new Book("978-4798161488", "MySQL徹底入門 第4版 MySQL 8.0対応", 4180, LocalDate.of(2020, 7, 6), mysqlCategory.id()) ); // insert categoryDao.insert(mysqlCategory); mysqlBooks.forEach(bookDao::insert); // update(title、priceのみ) bookDao.update(new Book("978-4621303252", "Effective Java", 3000, LocalDate.of(2018, 10, 30), javaCategory.id())); bookDao.update(new Book("978-4774189093", "Java本格入門", 2500, LocalDate.of(2017, 4, 18), javaCategory.id())); // delete bookDao.delete(new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520, LocalDate.of(2024, 10, 3), javaCategory.id())); // 終了 changes.setEndPointNow(); assertThat(changes) // 全体の変更数 .hasNumberOfChanges(5) // 種類ごとの変更数 .ofCreation().hasNumberOfChanges(2) .ofModification().hasNumberOfChanges(2) .ofDeletion().hasNumberOfChanges(1) // categoryテーブルの変更数 .onTable("category") .hasNumberOfChanges(0) // bookテーブルの変更数 .onTable("book") .hasNumberOfChanges(5); assertThat(changes) // ひとつ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4297141844") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .hasValues("978-4297141844", "MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ", 3080, DateValue.from(LocalDate.of(2024, 5, 22)), 2) // 2つ目の変更(insert) .change() .isOnTable("book") .isCreation() .hasPksNames("isbn") .hasPksValues("978-4798161488") // 開始時点の行 .rowAtStartPoint() .doesNotExist() // 終了時点の行 .rowAtEndPoint() .value("isbn").isEqualTo("978-4798161488") .value("title").isEqualTo("MySQL徹底入門 第4版 MySQL 8.0対応") .value("price").isEqualTo(4180) .value("publish_date").isEqualTo(DateValue.from(LocalDate.of(2020, 7, 6))) .value("category_id").isEqualTo(2) // 3つ目の変更(insert)は記録されない // 4つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4621303252") // 変更されたカラム .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Effective Java 第3版", "Effective Java") .column("price").hasValues(4400, 3000) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Effective Java 第3版") .value("price").isEqualTo(4400) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Effective Java") .value("price").isEqualTo(3000) // 5つ目の変更(update) .change() .isOnTable("book") .isModification() .hasPksNames("isbn") .hasPksValues("978-4774189093") .hasModifiedColumns("title", "price") // 変更前後のカラムの値を参照 .column("title").hasValues("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", "Java本格入門") .column("price").hasValues(3278, 2500) // 開始時点の行 .rowAtStartPoint() .value("title").isEqualTo("Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで") .value("price").isEqualTo(3278) // 終了時点の行 .rowAtEndPoint() .value("title").isEqualTo("Java本格入門") .value("price").isEqualTo(2500) // 6つ目の変更(delete) .change() .isOnTable("book") .isDeletion() .hasPksNames("isbn") .hasPksValues("978-4297144357") // 開始時点の行 .rowAtStartPoint() .value("isbn").isEqualTo("978-4297144357") .value("title").isEqualTo("Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~") // 終了時点の行 .rowAtEndPoint() .doesNotExist(); }); }
こういった感じですね。
Table book = assertDbConnection.table("book").build();
Changes changes = assertDbConnection.changes().tables(book).build();
今回はbook
テーブルのみを対象にしているので、category
テーブルの変更内容は検出されません。
// 3つ目の変更(insert)は記録されない
こんなところでしょうか。
オマケ:Changesでの変更内容の検出方法
Changes
がどうやって変更内容を検出しているのか気になったので、ちょっと見てみました。
結論としては、Changes#setStartPointNow
の時点でデータを取得しておき、Changes#setStartPointNow
を呼び出した時点でのデータも
取得してその差分を算出しているようです。
Changes#setStartPointNow
でデータを取得している箇所。
Changes#setEndPointNow
でデータを取得している箇所。
実際の差分抽出は、このあたりで行っています。
つまり、この仕組みを考えるとChanges
は変更内容がAssertJ-DBから見える状態でないと機能しないことになります。
以下の状態で使うことになりますね。
実際にはテスト対象と同じトランザクション内で使うことが多いのかなと思います。
おわりに
データベースのデータに対するアサーションができるAssertJ-DBを試してみました。
Table
とRequest
はすんなりいったものの、Changes
はかなりハマりました…。ちゃんとドキュメントを読み込むべきでした…。
少し癖のある感じはしますが、Changes
の変更検出はよいですね。慣れは必要ですが便利そうです。