Testcontainersでデータアクセスロジックのテストを行う方法

# はじめに
Testcontainersを使ってMySQLへのCRUDを行うデータアクセスロジックのテストコードを書いてみたので備忘録を残します。本記事内では抜粋したサンプルコードを掲載していますが、完全なソースコードは[GitHubリポジトリ](https://github.com/avenue68/repository-for-sharing/tree/qiita/797dab990acac61c94e9/qiita/797dab990acac61c94e9)で閲覧可能です。

# Testcontainersとは
[公式リファレンス](https://www.testcontainers.org#about)では以下のように説明されています。

>Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
（訳：TestcontainersはJUnitテストをサポートするJavaライブラリで、一般的なデータベースやSeleniumウェブブラウザなど、Dockerコンテナで実行可能なあらゆるものの軽量で使い捨てのインスタンスを提供します。）

つまり、データベースのような外部へのアクセスを伴うテストを行う際、そのアクセス先をテスト実行時のみ有効なDockerコンテナで代用することができるようになり、以下のようなメリットが得られます。

- アクセス先を開発用ローカルマシン内に立ち上げたり、ローカルネットワーク内に立ち上げたりする必要が無くなる
- テストコードや設定ファイルを共有するだけで同じテスト環境を共有できる
- テスト実施時はアクセス先が毎回同じ状態であることが保証される
- テスト終了後はコンテナが破棄され、テストの影響が残らない

今回は、単体テストコードからTestcontainersにより生成されたDockerコンテナ内のMySQLにアクセスし、実際のMySQLデータベースを用意することなくテストを実施できるようにします。

ここで解説する内容の多くは、JDBCを用いる場合であればMySQL以外のDBイメージにも共通の内容です。また、TestcontainersではDB以外にも、多くのDockerイメージに対応しています。利用可能なイメージの一覧は、[公式リファレンス](https://www.testcontainers.org/)の「Modules」を参照してください。

# 環境
- Java
- Junit5
- Spring Boot
- MyBatis
- Maven
- Windows10（※Testcontainers実行時はDocker Desktopを起動しておく必要があります）

# 依存関係
JUnit5でTestcontainersを利用するための依存関係として`org.testcontainers:junit-jupiter`を、TestcontainersでMySQLイメージを利用するための依存関係としてを`org.testcontainers:mysql`それぞれ`<dependencies>`に追加する必要があります。また、Testcontainersで使用するDBイメージを依存関係に追加していても、そのDB用のJDBCドライバは別途クラスパス上に存在する必要があるため、MySQL用のJDBCドライバも`<dependencies>`に追加します。

```xml:pom.xml(抜粋)
<dependencies>
	...
	<dependency>
		<groupId>org.testcontainers</groupId>
		<artifactId>junit-jupiter</artifactId>
		<version>1.16.2</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.testcontainers</groupId>
		<artifactId>mysql</artifactId>
		<version>1.16.2</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
		<!-- Spring Boot利用のためバージョンは省略 -->
	</dependency>
	...
</dependencies>
```

[公式リファレンス](https://www.testcontainers.org/#managing-versions-for-multiple-testcontainers-dependencies)ではそれぞれの依存関係で個別に`<version>`を明示する代わりに、`<dependencyManagement>`にTestcontainersの[BOM](https://qiita.com/syogi_wap/items/432bbdbe9892eb05e122)を追加することを推奨しています。

```xml:pom.xml(抜粋)
<dependencyManagement>
	<dependencies>
		...
		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>testcontainers-bom</artifactId>
			<version>1.16.2</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		...
	</dependencies>
</dependencyManagement>
<dependencies>
	...
	<dependency>
		<groupId>org.testcontainers</groupId>
		<artifactId>junit-jupiter</artifactId>
		<!-- <version>1.16.2</version> BOM利用時は省略可-->
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.testcontainers</groupId>
		<artifactId>mysql</artifactId>
		<!-- <version>1.16.2</version> BOM利用時は省略可-->
		<scope>test</scope>
	</dependency>
	...
</dependencies>
```

BOMを追加することで、それぞれのTestcontainers依存関係のバージョンはBOMのバージョンに対応するものが自動的に選択されます。

# コンテナの生成方法
Testcontainersでテストコードからアクセス可能なMySQLコンテナを生成するための方法として、公式リファレンスでは以下の2つが提示されています。

**1. [Testcontainers用JDBC URL](https://www.testcontainers.org/modules/databases/jdbc/#database-containers-launched-via-jdbc-url-scheme)を使用しコンテナの生成を指示する方法**
**2. [Database container objects](https://www.testcontainers.org/modules/databases/jdbc/#database-container-objects)を使用しテストコード内で明示的にコンテナを生成する方法**

公式リファレンスでは**1.Testcontainers用JDBC URL**を用いる方法をまず検討し、その方法が採用できない制約がある場合や、DBコンテナを細かく設定したい場合に**2.Database container objects**を用いる方法を採用するよう推奨しています。
今回は**1.Testcontainers用JDBC URL**を用いた方法を採用しました。

また、詳細は後述しますが、**1.Testcontainers用JDBC URL**を用いる方法ではテストコード内にコンテナを生成するための処理を記述する必要が無く、Testcontainers用JDBC URLを実際に接続するDB用のJDBC URLに変更すれば、そのまま実際のDBを使用したテストに切り替えることもできます。

## Testcontainers用JDBC URLの形式
実際のJDBC URLを以下の手順で修正することでTestcontainers形式のJDBC URLになり、テスト実行時に指定したイメージのDBコンテナが生成されるようになります。Spring Bootを使用している今回の例であれば、`spring.datasource.url`プロパティにTestcontainers用JDBC URLを指定すると、Testcontainersが有効になります。

1. `jdbc:`の後ろに`tc:`を挿入する
1. `ドライバ名`を`Dockerイメージ名:タグ名`に置き換える

```diff
- jdbc:mysql://localhost:3306/databasename
+ jdbc:tc:mysql:8.0://localhost:3306/databasename
（「tc」を挿入し、「mysql」というドライバ名を「mysql:8.0」というDockerイメージ名に置換）
```

また、Testcontainers用JDBC URL内では`ホスト名:ポート番号/データベース名`は無視されるので、最終的に以下のように短縮できます。

```diff
- jdbc:tc:mysql:8.0://localhost:3306/databasename
+ jdbc:tc:mysql:8.0:///
（「ホスト名:ポート番号/データベース名」を除去）
```

この短縮形式を`host-less URIs`と呼びます。

```
jdbc:tc:[Dockerイメージ名]:[タグ名]///
```

JDBC URLを見たとき、Testcontainersを使用していることがすぐに分かるため、テスト用のJDBC URLには`host-less URIs`を使うことをお勧めします。

TestcontainersではJDBC URLの短縮有無によらず、生成されるコンテナのデータベース名，使用されるユーザー名やパスワードは一律`test`に固定されます。これらを変更したい場合は、前述の[Database container objects](https://www.testcontainers.org/modules/databases/jdbc/#database-container-objects)によりコンテナを生成してください。

公式リファレンス内の解説では、

>Note that the hostname, port and database name will be ignored;（訳: ホスト名，ポート番号，DB名は無視されます）

と明記されているにもかかわらず、`jdbc:mysql:8.0:///databasename`のようにDB名のみを指定したJDBC URLが例示されていますが、2022/02現在、指定したDB名は生成されるコンテナに反映されません。GitHubの[issue](https://github.com/testcontainers/testcontainers-java/issues/4121)でこの点について議論されています。

## オプションの指定方法
Testcontainers用JDBC URLには、クエリパラメータ形式でオプションを指定することができます。複数のオプションを指定する場合もクエリパラメータの形式同様に`&`で連結します。
オプションの種類はいくつかありますが、ここでは`TC_INITSCRIPT`について解説します。その他のオプションについては[公式リファレンス](https://www.testcontainers.org/modules/databases/jdbc/#using-a-classpath-init-script)を参照してください。DBイメージで固有のオプション等も存在します。

### TC_INITSCRIPT

テストコードの実行前に、生成されたDBコンテナを初期化するためのSQLファイルを実行させるためのオプションです。Testcontainersにより生成されたDBコンテナは空っぽの状態で、テーブルもテスト用データも含まれていません。そのため、このオプションを用いてDDLやテストデータの投入を行うSQLをあらかじめ実行しておくよう指示します。
このオプションを使用するためには、Testcontainers用JDBC URLにクエリパラメータとして

```
TC_INITSCRIPT=[初期化用SQLファイルのパス]
```

を付加します。パスは「クラスパスルートからの相対パス」で指定してください。

```
jdbc:tc:mysql:8.0:///?TC_INITSCRIPT=path/to/ddl.sql
（DBコンテナ生成後に、「ddl.sql」というSQLファイルを実行させる場合の例）
```

# テストコードの記述方法
実際にTestcontainersにより生成されるDBコンテナを使用するテストコードを記述する方法について解説します。
初めにサンプルテストコードを掲載し、それぞれのポイントについて解説していきます。

```java:UserDaoTest.java(抜粋)
@Testcontainers // (1)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // (2)
@MybatisTest // (3)
@Sql("classpath:path/to/testdata.sql") // (4)
class UserDaoTest {

	@Autowired
	private UserDao dao;
	
	@Test
	void testUserDao() {
		// (5)
	}
}
```

**(1) @&#x200b;Testcontainers**
テストクラスでTestcontainersにより生成されるDockerコンテナを使用することを宣言するためのアノテーションです。テストクラスにこのアノテーションを付与することで、テスト実行前にJDBC URLに基づき、DBコンテナが生成・初期化されます。

```
@Testcontainers(disabledWithoutDocker = true)
```

のように`disableWithoutDocker`パラメータに`true`を指定すると、テスト実行時にDockerが利用できなかった場合はテストがスキップされるようになります。（WindowsPCを使っている場合、Docker Desktopを起動せずにテストを実行するとテストがスキップされることが確認できます。）

**(2) `@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)`**
Spring Bootでデフォルトで有効になっているテスト用組込DBを無効化するためのアノテーションです。本アノテーションを付与する代わりに、`spring.test.database.replace`プロパティに`none`を指定することでも同様の効果があります。

**(3) `@MybatisTest`**
今回はO/RマッパーとしてMyBatisを使用するため、テストコード内でMyBatis関連のAutoConfigureクラス，MapperクラスのBeanのみが有効になるよう本アノテーションを付与しています。
`@MyBatisTest`については、[こちらの記事](https://qiita.com/avenue68/items/e066e672c3c99235aba1)を参照してください。

**(4) `@Sql`**
各テストメソッド実行前に、指定されたSQLファイルを実行してくれるアノテーションです。DBコンテナにテストデータを投入する用途で使用しています。
前述の`TC_INITSCRIPT`オプションでもSQLファイルを用いたDBコンテナの初期化を行うことができますが、`TC_INITSCRIPT`オプションではSQLファイルを1つしか指定することができません。そのため、DDLとテストデータ投入を1つのSQLファイル内に記述する必要があります。
DDLとテストデータ投入は別のSQLファイルで管理したい場合や、テストケースに応じて投入するテストデータを切り替えたい場合は、本サンプルのように`@Sql`アノテーションでテストデータの投入を行っています。その他、DBUnitなどのツールもTestcontainersと併用可能です。

**(5) テストメソッドの実装**
各テストメソッド内ではTestcontainersを意識することなく、実際のDBを用いる場合と全く同じようにテストコードを記述することができます。

# プロパティファイルの記述方法
本サンプルではSpring Bootを用いているので、Testcontainersに関するプロパティファイルの記述についても解説します。

```properties
spring.datasource.url=${DATASOURCE:jdbc:tc:mysql:8.0:///?TC_INITSCRIPT=path/to/ddl.sql}
spring.datasource.username=${DATASOURCE_USER:}
spring.datasource.password=${DATASOURCE_PASSWORD:}

spring.test.database.replace=none
```

**spring.datasource.url**
Testcontainers用JDBC URLを指定します。
このサンプルでは`${環境変数名:デフォルト値}`の形式でプロパティ値を指定しており、環境変数`DATASOURCE`に値が設定されていればその値をJDBC URLとして使用し、設定されていなければTestcontainers用JDBC URLを使用するようになっています。
JDBC URLがTestcontainers用の形式でなければ、テストクラスに前述の`@Testcontainers`アノテーションが付与されていてもDBコンテナの生成は行われません。そのため、ステージング環境などでは環境変数を設定しておくことで実際のDBを用いたテストを行い、ローカル環境ではDBコンテナを用いたテストを行うというようなことが、テストコードを修正したり複数用意したりすることなく実現できます。

**spring.datasource.username，spring.datasource.password**
こちらも`${環境変数:デフォルト値}`の形式ですが、デフォルト値を指定していません。前述のとおり、Testcontainers用JDBC URL使用時はユーザー名やパスワードは`test`で固定されますが、他の値を指定したり、あるいは何も指定しなくても認証エラーにはならないので、このようにプロパティ値を指定しています。

**spring.test.database.replace**
Spring Bootでデフォルトで有効になっているテスト用組込DBを無効化するための指定です。
前述した`@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)`アノテーションを使う代わりに、プロパティファイルで設定する場合の記述です。

# テスト実行
通常のテストと同様に、EclipseやMavenで実行することができます。
以下はテストを実行した際に出力されるログです。（指定したDockerイメージをTestcontainersで初めて使用する場合は数分以上時間がかかります。）

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

2022-01-31 10:55:26.500  INFO 16468 --- [           main] com.example.UserDaoTest                  : Starting UserDaoTest using Java 17.0.1 on with PID 16468 (started by)
2022-01-31 10:55:26.502  INFO 16468 --- [           main] com.example.UserDaoTest                  : No active profile set, falling back to default profiles: default
2022-01-31 10:55:27.819  INFO 16468 --- [           main] com.example.UserDaoTest                  : Started UserDaoTest in 1.891 seconds (JVM running for 3.261)
2022-01-31 10:55:27.868  INFO 16468 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-31 10:55:28.130  INFO 16468 --- [           main] o.t.d.DockerClientProviderStrategy       : Loaded org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2022-01-31 10:55:28.723  INFO 16468 --- [           main] o.t.d.DockerClientProviderStrategy       : Found Docker environment with local Npipe socket (npipe:////./pipe/docker_engine)
2022-01-31 10:55:28.725  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : Docker host IP address is localhost
2022-01-31 10:55:28.779  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : Connected to docker: 
  Server Version: 20.10.11
  API Version: 1.41
  Operating System: Docker Desktop
  Total Memory: 4711 MB
2022-01-31 10:55:28.784  INFO 16468 --- [           main] o.t.utility.ImageNameSubstitutor         : Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2022-01-31 10:55:29.215  INFO 16468 --- [           main] o.t.utility.RegistryAuthLocator          : Credential helper/store (docker-credential-desktop) does not have credentials for index.docker.io
2022-01-31 10:55:32.569  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2022-01-31 10:55:32.570  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : Checking the system...
2022-01-31 10:55:32.571  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : ✔︎ Docker server version should be at least 1.6.0
2022-01-31 10:55:32.706  INFO 16468 --- [           main] org.testcontainers.DockerClientFactory   : ✔︎ Docker environment should have more than 2GB free disk space
2022-01-31 10:55:32.725  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Creating container for image: mysql:8.0
2022-01-31 10:55:32.879  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Starting container with ID: 466df4de50fe211466427c0c56d673ba8ebb9b4edbf8f124eb654560614bb8a2
2022-01-31 10:55:33.253  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Container mysql:8.0 is starting: 466df4de50fe211466427c0c56d673ba8ebb9b4edbf8f124eb654560614bb8a2
2022-01-31 10:55:33.325  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Waiting for database connection to become available at jdbc:mysql://localhost:62119/test using query 'SELECT 1'
2022-01-31 10:55:48.318  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Container is started (JDBC URL: jdbc:mysql://localhost:62119/test)
2022-01-31 10:55:48.318  INFO 16468 --- [           main] 🐳 [mysql:8.0]                           : Container mysql:8.0 started in PT15.6056995S
2022-01-31 10:55:48.353  INFO 16468 --- [           main] org.testcontainers.ext.ScriptUtils       : Executing database script from META-INF/mysql/ddl.sql
2022-01-31 10:55:48.406  INFO 16468 --- [           main] org.testcontainers.ext.ScriptUtils       : Executed database script from META-INF/mysql/ddl.sql in 52 ms.
2022-01-31 10:55:48.420  INFO 16468 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-01-31 10:55:48.430  INFO 16468 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@3576ddc2 testClass = UserDaoTest, testInstance = com.example.UserDaoTest@cd1e646, testMethod = testDelete@UserDaoTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@35b74c5c testClass = UserDaoTest, locations = '{}', classes = '{class com.example.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.mybatis.spring.boot.test.autoconfigure.MybatisTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2e570ded key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@61001b64, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@26e356f0, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@272113c4, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@3f6f6701, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@9647a2c9, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13e344d, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.jdbc.support.JdbcTransactionManager@73608eb0]; rollback [true]
2022-01-31 10:55:49.394  INFO 16468 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@3576ddc2 testClass = UserDaoTest, testInstance = com.example.UserDaoTest@cd1e646, testMethod = testDelete@UserDaoTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@35b74c5c testClass = UserDaoTest, locations = '{}', classes = '{class com.example.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.mybatis.spring.boot.test.autoconfigure.MybatisTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2e570ded key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@61001b64, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@26e356f0, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@272113c4, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@3f6f6701, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@9647a2c9, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13e344d, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
2022-01-31 10:55:49.411  INFO 16468 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@3576ddc2 testClass = UserDaoTest, testInstance = com.example.UserDaoTest@266da047, testMethod = testSelect@UserDaoTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@35b74c5c testClass = UserDaoTest, locations = '{}', classes = '{class com.example.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.mybatis.spring.boot.test.autoconfigure.MybatisTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2e570ded key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@61001b64, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@26e356f0, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@272113c4, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@3f6f6701, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@9647a2c9, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13e344d, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.jdbc.support.JdbcTransactionManager@73608eb0]; rollback [true]
2022-01-31 10:55:49.456  INFO 16468 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@3576ddc2 testClass = UserDaoTest, testInstance = com.example.UserDaoTest@266da047, testMethod = testSelect@UserDaoTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@35b74c5c testClass = UserDaoTest, locations = '{}', classes = '{class com.example.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.mybatis.spring.boot.test.autoconfigure.MybatisTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2e570ded key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration, org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@61001b64, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@26e356f0, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@272113c4, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@3f6f6701, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@9647a2c9, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13e344d, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
2022-01-31 10:55:49.468  INFO 16468 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-01-31 10:55:50.017  INFO 16468 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

```

# 参考
- https://www.testcontainers.org/
- https://b1a9idps.com/posts/test-containers/
- https://zenn.dev/kentama/articles/aa7bbe728845da

ご意見・ご指摘等あればコメントいただけると幸いです。