Spring Test DBUnitでテストする時、テスト用スキーマに切り替える

# はじめに
[以前の記事](https://qiita.com/kaburankattara/items/ba738eae9905d51d2b88)でDBUnitを使ったテストを実装しました。
今回はテストする時に専用のスキーマ(MySQLの場合データベース)に切り替えるようにします。

# 実行環境
OS：Mac
Spring Test DBUnit：spring-test-dbunit 1.3.0
DBUnit：DBUnit 2.7.3
JUnit：JUnit5.8.2
Java：Amazon Corretto 8
Spring Boot：2.6.1
Gradle：gradle6.8
IDE：intellij idea community edition 2021.1
DB：MySQL8.0

# テスト用のDBを作成
まず、テストする時に使用するDBを作成します。
テスト用に「helloworldTest」というDBを作成し、
空っぽの「hello_world」テーブルを作成します。

```roomsql
CREATE DATABASE helloworldTest;
USE helloworldTest;

create table hello_world (
  id VARCHAR(10) default '' not null
  , name VARCHAR(50) default '' not null
  , unique hello_world_index (id)
) ;
```

# テスト用にデータソースの設定する
テスト用パッケージにデータソースのBeanを実装します。
以下のようにDataSourceオブジェクトを生成し、
DataSourceにはテスト用に用意した「helloworldTest」を指定します。

```
helloworld/
　└ src/
　 　└ main/
　 　│　
　 　└ test/
　 　 　└ java/
    　 　 　└ パッケージ/
        　 　 　└ config/
        　 　 　   └ DatasourceConfig.java
```

```java
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

import javax.sql.DataSource;

@Configuration
public class DatasourceConfig {

    @Bean
    public DataSource dataSource() {
        return new TransactionAwareDataSourceProxy(
                DataSourceBuilder
                        .create()
                        .username("root")
                        .password("my-secret-pw")
                        .url("jdbc:mysql://localhost/helloworldTest")
                        .driverClassName("com.mysql.cj.jdbc.Driver")
                        .build());
    }
}
```

# 専用DBでテスト開始（失敗）
DataSourceでDBの繋ぎ先を切り替えたので、テスト実行！
以下のExceptionがthrowされてしまいました。

```
org.dbunit.database.AmbiguousTableNameException: HELLO_WORLD
	at org.dbunit.dataset.OrderedTableNameMap.add(OrderedTableNameMap.java:198) ~[dbunit-2.7.3.jar:na]
	at org.dbunit.database.DatabaseDataSet.initialize(DatabaseDataSet.java:244) ~[dbunit-2.7.3.jar:na]
	at org.dbunit.database.DatabaseDataSet.getTableMetaData(DatabaseDataSet.java:298) ~[dbunit-2.7.3.jar:na]
	at org.dbunit.operation.DeleteAllOperation.execute(DeleteAllOperation.java:109) ~[dbunit-2.7.3.jar:na]
	at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79) ~[dbunit-2.7.3.jar:na]
	at com.github.springtestdbunit.DbUnitRunner.setupOrTeardown(DbUnitRunner.java:183) ~[spring-test-dbunit-1.3.0.jar:na]
	at com.github.springtestdbunit.DbUnitRunner.beforeTestMethod(DbUnitRunner.java:75) ~[spring-test-dbunit-1.3.0.jar:na]
	at com.github.springtestdbunit.DbUnitTestExecutionListener.beforeTestMethod(DbUnitTestExecutionListener.java:185) ~[spring-test-dbunit-1.3.0.jar:na]
	at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:293) ~[spring-test-5.3.13.jar:5.3.13]
	at org.springframework.test.context.junit.jupiter.SpringExtension.beforeEach(SpringExtension.java:174) ~[spring-test-5.3.13.jar:5.3.13]
	// ・・・
```

## AmbiguousTableNameException

[こちらのページ](https://www.dbunit.org/apidocs/org/dbunit/database/AmbiguousTableNameException.html)で調べて見ると、
同じ名前のテーブルが（DBを跨いでも）複数存在するとスローされるようです。

解決策として、以下の3つが挙げられていました。
1）1つのデータベーススキーマにのみアクセスできるデータベース接続クレデンシャルを使用する。
2）DatabaseConnectionまたは DatabaseDataSourceConnectionコンストラクターにスキーマ名を指定する。
3）修飾テーブル名のサポートを有効にする。

結果として2)、3)を実施することで解決出来ました。

## 問題の部分1
スタックトレースより、DatabaseDataSetクラスでテーブル名を取得している箇所を発見しました。
以下の207行目より、metadataHandlerにdatabaseMetaDataやschemaを指定してテーブルを取得していました。

しかし、デバッグするとschemaがnullだったため、上手く指定出来ていなかったことがわかります。

```java
204         String[] tableType = (String[])config.getProperty(DatabaseConfig.PROPERTY_TABLE_TYPE);
205         IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
206
207         ResultSet resultSet = metadataHandler.getTables(databaseMetaData, schema, tableType);
```

なので、DatabaseDataSourceConnectionにDB（スキーマ）を指定します。

```java
	@Bean
	public IDatabaseConnection dbUnitDatabaseConnection(DataSource dataSource) throws SQLException {
		return new DatabaseDataSourceConnection(dataSource, "helloworldTest");
	}
```

## 問題の部分2
スキーマを指定したが、まだ改善しませんでした。
どうやら、DatabaseDataSetクラスの205行目でmetadataHandlerからスキーマを取得するのに失敗していたようです。

```java
220        _schemaSet.add(schema);
221        while (resultSet.next())
222        {
223        String schemaName = metadataHandler.getSchema(resultSet);
224        String tableName = resultSet.getString(3);
```

なのでmetadataHandlerを調べて見ると[ドキュメント](https://www.dbunit.org/properties.html)に記載がありました。

データベースメタデータ関連のメソッドを制御するために使用されるハンドラーを構成するために使用されます。
オブジェクトは org.dbunit.database.IMetadataHandlerを実装する必要があります。
とのこと

もう一度、DatabaseDataSourceConnectionを修正します。
MySqlMetadataHandlerでIMetadataHandlerを実装します。

```java
	@Bean
	public IDatabaseConnection dbUnitDatabaseConnection(DataSource dataSource) throws SQLException {
		IDatabaseConnection con = new DatabaseDataSourceConnection(dataSource, "helloworldTest");
		con.getConfig().setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER, new MySqlMetadataHandler());
		return con;
	}
```

# 専用DBでテスト開始（成功）
以上を踏まえてテストを実行すると、上手く動きました。

以下、最終的なDatasourceConfigです。

```java
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseDataSourceConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.ext.mysql.MySqlMetadataHandler;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
public class DatasourceConfig {

    @Bean
	public DataSource dataSource() {
		return new TransactionAwareDataSourceProxy(
				DataSourceBuilder
						.create()
						.username("root")
						.password("my-secret-pw")
						.url("jdbc:mysql://localhost/helloworldTest")
						.driverClassName("com.mysql.cj.jdbc.Driver")
						.build());
	}

	@Bean
	public IDatabaseConnection dbUnitDatabaseConnection(DataSource dataSource) throws SQLException {
		IDatabaseConnection con = new DatabaseDataSourceConnection(dataSource, "helloworldTest");
		con.getConfig().setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER, new MySqlMetadataHandler());
		return con;
	}

}
```


# 参考文献
https://www.dbunit.org/apidocs/org/dbunit/database/AmbiguousTableNameException.html

https://www.dbunit.org/properties.html