【Spring Boot】DbSetupを使ったRepositoryの単体テスト

DbSetupを使ったRepositoryの単体テストの書き方を学んだので、備忘のために執筆します。

# はじめに
## DbSetupとは
データベースの単体テストを行うために用意されたライブラリ。
テストデータ投入手段として使用でき、少量のテストデータ投入に向いている。

[DbSetup 公式](http://dbsetup.ninja-squad.com/)

## DbUnitとは
DbSetupと同じく、データベースの単体テストを行うために用意されたライブラリで、テストデータ投入手段として使用できる。
DbSetupとの違いとしては、DbSetupはテストデータをJavaで記載していくのに対して、
DbUnitはXMLやExcelでテストデータを準備するようになっている。

## DbSetup 選択理由
DbUnitではなくDbSetupを選ぶ理由
- 単体テストで必要になるデータは少量のテストデータが多く、DbUnitだとその都度XMLやExcelでファイルを作成する必要があり手間であるが、DbSetupならJavaで投入データを作成していくので、手間が軽減する。
- Javaで記述していくDbSetupと違って、DbUnitだとテストデータを外部ファイルに保存しておくので、どのファイルとどのテストクラスが結びついているのかがわかりにくい。

# DbSetupを使用したテストコードの作成
DbSetupを使用したテストコードの作成を行う。
今回はユーザー情報を保存しておくテーブル（usersテーブル）を作成し、ユーザー情報をちゃんと登録できているかをテストするテストコードを作成する。
DBへのアクセスはJDBCを使用する。

## 開発環境・実行環境
- Spring Tool Suite 4 Version: 4.14.1.RELEASE
- Spring boot 2.7.3
- Java17
- Marvin 3.8.4
- PostgreSQL 11.15

## pom.xml
DbSetupを依存関係に追加する。
~~~xml:pom.xml
<!-- ▼ DbSetup ▼ -->
<dependency>
  <groupId>com.ninja-squad</groupId>
  <artifactId>DbSetup-kotlin</artifactId>
  <version>2.1.0</version>
  <scope>test</scope>
</dependency>
~~~

## sql
id、名前、メールアドレスだけの簡単なテーブルを用意する
~~~sql:sql
create table users(
	id serial PRIMARY KEY,
	name text not null,
	email text not null
)
~~~

## domain
usersテーブルに対応するUserクラスを作成。
~~~java:User.java
package com.example.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
	
	private Integer id;
	private String name;
	private String email;

}
~~~

## repository
DBとやり取りするUserRepositoryクラスを作成。
~~~java:UserRepository.java
package com.example.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import com.example.domain.User;

/**
 * usersテーブルを操作するリポジトリ
 */
@Repository
public class UserRepository {
	
	@Autowired
	private NamedParameterJdbcTemplate template;
	
	/** RowMapper **/
	public static final RowMapper<User> USER_ROW_MAPPER = (rs, i) -> {
		User user = new User();
		user.setId(rs.getInt("id"));
		user.setName(rs.getString("name"));
		user.setEmail(rs.getString("email"));
		
		return user;
	};
	
	/**
	 * userを1件登録します
	 * @param user
	 * @return 登録したuser（自動採番されたidも含む）
	 */
	public User insert(User user) {
		String sql = "INSERT INTO users (name, email) VALUES (:name, :email)";
		SqlParameterSource param = new BeanPropertySqlParameterSource(user);
		
		KeyHolder keyHolder = new GeneratedKeyHolder();
		String[] keyColumnNames = {"id"};
		template.update(sql, param, keyHolder, keyColumnNames);
		user.setId(keyHolder.getKey().intValue());
		
		return user;
		
	}

}

~~~

ここまでできたら実装は完了。
次に今回の目的であるテストコードを書いていく。

## 【本題】テストコードの作成
必要なもの
- [UserOperation.java](https://qiita.com/MMK3/items/731d609228b0589855d2#useroperation)
    テストデータを書いていくクラス
- [AccessConfig.java](https://qiita.com/MMK3/items/731d609228b0589855d2#accessconfig)
    DBSetupを使用する際にDBと接続するために必要なクラス
- [UserRepositoryTest.java](https://qiita.com/MMK3/items/731d609228b0589855d2#userrepositorytest)
    JUnitを使ってテストコード書いていくクラス

### UserOperation
~~~java:UserOperation.java
package com.example.repository.operation;

import static com.ninja_squad.dbsetup.Operations.insertInto;
import static com.ninja_squad.dbsetup.Operations.deleteAllFrom;
import org.springframework.stereotype.Component;

import com.ninja_squad.dbsetup.operation.Operation;

/**
 * Userテーブルのオペレーション
 *
 */
public class UserOperation {
	
	/** テストデータ 投入 **/
	public static final Operation USER_INSERT = insertInto("users")
			.columns("name", "email")
			.values("佐藤太郎", "test1@test.com")
			.values("高橋花子", "test2@test.com")
			.values("山田一郎", "test3@test.com")
			.build();
	
	/** 全レコード 削除 **/
	public static final Operation USER_DELETE = deleteAllFrom("users");

}

~~~
`Operations`クラスに定義されたstaticメソッドを使ってテストデータを作成する。

### AccessConfig
~~~java:AccessConfig.java
package com.example.repository;

import org.springframework.stereotype.Component;

import com.ninja_squad.dbsetup.destination.Destination;
import com.ninja_squad.dbsetup.destination.DriverManagerDestination;

/**
 * DBSetupを使用する際にDBと接続するために必要なクラス
 * 接続に必要なDestinationオブジェクトを作成
 *
 */
public class AccessConfig {
	
	/** 接続に必要なDestinationオブジェクトを作成 **/
	public static final Destination dest = new DriverManagerDestination(
			"jdbc:postgresql://localhost:5432/【DB名】", 
			"【user】", 
			"【password】"
			);

}
~~~

`Destination`オブジェクトを作成するには2通りの方法があるようだが、
今回は`DriverManagerDestination`を使用していく
~~~java:Destinationオブジェクトを作成する2通りの方法
// (1) javax.sql.DataSourceから作成する
Destination dest = new DataSourceDestination(datasource);

// (2) URL, User, Passwordから作成する ※今回はこれを使用する
Destination dest = new DriverManagerDestination(url, user, password);
~~~

### UserRepositoryTest
~~~java:UserRepositoryTest.hava
package com.example.repository;

import static com.ninja_squad.dbsetup.Operations.sequenceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.domain.User;
import com.example.repository.operation.UserOperation;
import com.ninja_squad.dbsetup.DbSetup;
import com.ninja_squad.dbsetup.operation.Operation;

@SpringBootTest
class UserRepositoryTest {
	
	@Autowired
	private UserRepository userRepository;
	
	private static DbSetup dbSetup;

	/**
	 * 実行前DBと接続し、削除と登録を行う
	 * @throws Exception
	 */
	@BeforeAll
	static void setUpBeforeClass() throws Exception {
        // UserOperationクラスで先ほど作成したデータをsequenceOf()メソッドの中に繋げて記述
		Operation operation = sequenceOf(UserOperation.USER_DELETE, UserOperation.USER_INSERT);

        // dbSetupに、先ほど作成したDestinationオブジェクトと、テストデータを渡す
		dbSetup = new DbSetup(AccessConfig.dest, operation);
        
        // DBと接続する
		dbSetup.launch();
	}

	@AfterAll
	static void tearDownAfterClass() throws Exception {
	}

	@BeforeEach
	void setUp() throws Exception {
	}

	@AfterEach
	void tearDown() throws Exception {
	}

	@Test
	@DisplayName("ユーザーを1件登録できているか")
	void insertTest() {
		// 期待値（登録するユーザー）
		User expected = new User();
		expected.setName("テスト太郎");
		expected.setEmail("test@test.com");
		
		// 実行
		User actual = userRepository.insert(expected);
		
		// 比較
		assertEquals(expected.getName(), actual.getName());
		assertEquals(expected.getEmail(), actual.getEmail());
	}

}

~~~

# 終わりに
DbSetupを用いて簡易なRepositoryの単体テストを作成しました。DbSetupを使ってRipositoryのテストをサクサクと書けるように習得していきたいです。
間違っている部分もあるかもしれませんので、その際はご指摘頂ければと思います。

# 参考文献
[JavaでのDBのテストデータ作成はDbSetupが楽](https://qiita.com/ikemo/items/a27ece72fe2af3683ff6)
[Spring + MyBatis + DbSetupでテストコードを書いてみる](https://qiita.com/ot-aoyagi/items/d6a367af8bac3cf67a86)
[SpringのUTにDBSetupを使ってみたら良かった](https://kensanty.hatenablog.com/entry/2017/01/09/133825)
[DbSetup, from Ninja Squad - Download](http://dbsetup.ninja-squad.com/download.html)