TestContainersとSpring Data JDBCを触ってみて自分なりにまとめてみました。
TestContainersとは
TestContainersはデータベース、Selenium Webブラウザー、などのインスタンスを提供します。
TestContainersを使用すると次の種類のテストが簡単になるようです。
- データアクセスレイヤに対するインテグレーションテスト
- MySQL、PostgreSQL、Oracleデータベースのコンテナ化されたインスタンスを使用
- コンテナ化できる他のデータベースも使用可能
- アプリケーションインテグレーションテスト
- データベース、メッセージキューなどに依存関係があるアプリケーションレイヤーのテストコードに対するテスト
- UI/受け入れテスト
- Seleniumと互換性のあるコンテナ化されたWebブラウザを使用
TestContainersを使うためには(事前準備)
環境
- Windows 10 Pro
- Java 11
- Apache Maven 3.8.2
- Docker 20.10.11
TestContainersを依存関係に加える
TestContainersを使うための準備です。
Dockerの要件をみて、version等に問題ないか確認します。
https://www.testcontainers.org/supported_docker_environment/
windowsの場合はこちらも確認しといた方がよさそうです。
https://www.testcontainers.org/supported_docker_environment/windows/
データベースはPostgreSQLを使用してみます。
https://www.testcontainers.org/modules/databases/postgres/
テストフレームワークはJUnit5を使用することにします。
JUnit5を使用する場合には、junit-jupiter
を追加する必要があります。
なので、pomはこんな感じ。
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.16.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.16.2</version>
<scope>test</scope>
</dependency>
SpringBootの依存関係
SpringBoot 2.6.1を使用します。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/>
</parent>
データベースアクセスにはSpring Data JDBCを使います。
追加したライブラリはこんな感じ。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Spring Data JDBCを使用したデータベースアクセス
接続設定
application.propertiesにはPostgreSQLへ接続するための設定情報を記述していきます。
後述しますが、spring.datasource.url
は記述していないです。
テストコード実行時に接続先のコンテナのURLを動的に割り当てます。
また今回はSpringBootのテーブルの初期化機能を使います。
schema.sqlやdata.sqlを用意し、DDLを記述します。
spring.sql.init.mode=always
としています。
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=username
spring.datasource.password=password
spring.sql.init.mode=always
次にDDLを記述します。(insert文も書いています。)
create table stock
(
id integer,
name text,
quantity integer,
primary key (id)
);
insert into stock(id, name, quantity)
values (1, 'りんご', 5);
Spring Data JDBC
リファレンスはこのあたり。
サンプルはこのあたり。
https://github.com/spring-projects/spring-data-examples
テーブルに対応するオブジェクトです。
lombokを使用しています。
主キーにはSpringのアノテーション@Id
を付与しておきます。
@AllArgsConstructor
@Data
public class Stock {
@Id
private Integer id;
private String name;
private Integer quantity;
}
上記のオブジェクトを操作するために、
CrudRepository
を継承したRepository
インタフェースを作成します。
CrudRepository
を継承するだけで簡易なCRUD機能が提供されるので今回は継承するだけでもいいですが、
テキトーな自分で書いたSQLのメソッドも書いておきます。
public interface StockRepository extends CrudRepository<Stock, Integer> {
@Query("update stock set quantity=:quantity where id=:id")
Integer update(@Param("id") Integer id, @Param("quantity") Integer quantity);
}
動作確認
準備
それではテストコードを書いて動かしてみます。
まず以下のようなテストクラスを作ります。
@Testcontainers
@SpringBootTest
public class PostgresqlContainerTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>(DockerImageName.parse("postgres:12"))
.withUsername("username")
.withPassword("password")
.withDatabaseName("postgres")
@Autowired
StockRepository repository;
@DynamicPropertySource
static void setup(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
}
}
をみると、JupiterもしくはJUnit5を使用する場合、テストクラスには@Testcontainer
を付ける必要がありそうです。
DockerImageName.parse()
の引数にはイメージ名を指定します。
イメージにはPostgreSQLのversion12を指定しました。
@Container
をつけた変数にはstatic
で定義していますが、この場合にはテストメソッドが実行される前に開始されて最後のメソッドで停止されます。
その結果、テストメソッド間で共有されます。
staticフィールドでなければ、メソッド実行されるたびに開始されるようです。
@DynamicPropertyRegistry
を使って、起動中のコンテナへ接続するための情報を設定します。
実行
それでは最後に実行してみます。
@Test
void test() {
Assertions.assertThat(postgres.isRunning()).isTrue();
final Optional<Stock> stock = repository.findById(1);
Assertions.assertThat(stock.get()).isEqualTo(new Stock(1, "りんご", 5));
final Integer update = repository.update(1, 20);
Assertions.assertThat(update).isEqualTo(1);
}
まとめ
TestContainersを使用してテストコードの記述までやってみました。
h2でも統合テストはできますが一部のSQLが実行できない、なんてこともあると思うので
それに比べると便利だと思います。
ただ、テストの実行がやや重い。。
おわり。