JUnit5でMyBatisのテストをしたいからDatabase-Riderを試してみる。

# アプリをテストしたくなった
wildflyでダウンロードアプリでも作ろうってことで、いろいろ試行錯誤しながら作成中。
状態遷移をテストする必要がでたから、いい方法ないか探したらDBRiderが活躍してるとのこと。何一つ知らないから調べながらメモ程度に更新。

# 環境
* MyBatis - 3.5.15
* JUnit5 - 5.10.2
* Database Rider - 1.41.1


**※pom.xmlを一部省いて記載してます。**
```pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    
	<properties>
 		<version.org.mybatis>3.5.15</version.org.mybatis>
		<version.org.junit>5.10.2</version.org.junit>
		<version.com.github.database-rider>1.41.1</version.com.github.database-rider>
	</properties>
 
 	<dependencyManagement>
		<dependencies>
 			<dependency>
				<groupId>org.junit</groupId>
				<artifactId>junit-bom</artifactId>
				<version>${version.org.junit}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>${version.org.mybatis}</version>
		</dependency>
  
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
		</dependency>
 
		<dependency>
			<groupId>com.github.database-rider</groupId>
			<artifactId>rider-junit5</artifactId>
			<version>${version.com.github.database-rider}</version>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>com.github.database-rider</groupId>
					<artifactId>rider-core</artifactId>
				</exclusion>
			</exclusions>
			<classifier>jakarta</classifier>
		</dependency>

		<dependency>
			<groupId>com.github.database-rider</groupId>
			<artifactId>rider-core</artifactId>
			<version>${version.com.github.database-rider}</version>
			<scope>test</scope>
			<classifier>jakarta</classifier>
		</dependency>
	</dependencies>

 </project>
```
# 設定ファイル
#### mybatis-config.xml
```mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<environments default="development">
		<environment id="development">
			<transactionManager type="MANAGED"/>
			<dataSource type="POOLED">
				<property name="driver" value="org.postgresql.Driver" />
				<property name="url"
					value="jdbc:postgresql://localhost:5432/postgres" />
				<property name="username" value="postgres" />
				<property name="password" value="postgres" />
			</dataSource>
		</environment>
	</environments>

	<mappers>
		<mapper class="com.example.database.UserRepository"/>
	</mappers>
</configuration>
```

#### dbunit.yml
```dbunit.yml
caseInsensitiveStrategy: LOWERCASE
properties:
  schema: public
connectionConfig:
  driver: "org.postgresql.Driver"
  url: "jdbc:postgresql://localhost:5432/postgres"
  user: "postgres"
  password: "postgres"
```

# テーブル
#### table.yml
```com/example/database/UserRepository/table.yml
users:
  - id: 1
    name: "alice"
    age: 10
  - id: 2
    name: "bob"
    age: 10 
```

#### testUpdateBobsAge.yml
```com/example/database/UserRepository/expected/testUpdateBobsAge.yml
users:
  - id: 1
    name: "alice"
    age: 10
  - id: 2
    name: "bob"
    age: 11
```

#### testInsertCarol.yml
```com/example/database/UserRepository/expected/testInsertCarol.yml
users:
  - id: 1
    name: "alice"
    age: 10
  - id: 2
    name: "bob"
    age: 10
  - id: 3
    name: "Carol"
    age: 14
```

#### testDeleteBob.yml
```com/example/database/UserRepository/expected/testDeleteBob.yml
users:
  - id: 1
    name: "alice"
    age: 10
```

# コード
#### User.java
```com/example/entity/User.java
package com.example.entity;

public class User {

    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return this.age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
```

#### UserRepository.java
```com/example/database/UserRepository.java
package com.example.database;

import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.example.entity.User;

public interface UserRepository {
    public List<User> getAll();

    public boolean updateAgeById(@Param("id") long id, @Param("age") long age);

    public boolean insert(@Param("entity") User entity);

    public boolean deleteById(@Param("id") long id);
}
```

#### UserRepositoryTest.java
```com/example/repository/UserRepositoryTest.java
package com.example.repository;

import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import com.example.entity.User;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.ExpectedDataSet;
import com.github.database.rider.junit5.DBUnitExtension;

@ExtendWith(DBUnitExtension.class)
class UserRepositoryTest {

    static SqlSessionFactory sqlSessionFactory;
    static Configuration configuration;

    @BeforeAll
    static void beforeAll() throws IOException {
        try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }
    }

    SqlSession sqlSession;
    UserRepository userRepository;

    @BeforeEach
    void beforeEach() {
        this.sqlSession = sqlSessionFactory.openSession();
        this.userRepository = this.sqlSession.getMapper(UserRepository.class);
    }

    @AfterEach
    void afterEach() {
        this.sqlSession.close();
    }

    @Test
    @DataSet("com/example/database/UserRepository/table.yml")
    void testGetAll() {
        List<User> users = this.userRepository.getAll();

        assertEquals(1L, users.get(0).getId());
        assertEquals(2L, users.get(1).getId());
    }

    @Test
    @DataSet("com/example/database/UserRepository/table.yml")
    @ExpectedDataSet("com/example/database/UserRepository/expected/testUpdateBobsAge.yml")
    void testUpdateBobsAge() {
        this.userRepository.updateAgeById(2L, 11L);
    }

    @Test
    @DataSet("com/example/database/UserRepository/table.yml")
    @ExpectedDataSet("com/example/database/UserRepository/expected/testInsertCarol.yml") 
    void testInsert() {
        User entity = new User();
        entity.setId(3L);
        entity.setName("Carol");
        entity.setAge(14);
        this.userRepository.insert(entity);
    }

    @Test
    @DataSet("com/example/database/UserRepository/table.yml")
    @ExpectedDataSet("com/example/database/UserRepository/expected/testDeleteBob.yml")
    void testDeleteBob() {
        this.userRepository.deleteById(2L);
    }
}

```

# メモ
* dbunit.yml の connectionConfig 項目を省きたい
    * MyBatis 設定ファイルから DBUnit に設定する方法ないか？
* @BeforeEachと@AfterEachのopen-close処理とSqlSession#getMapperを省きたい
    * dbrider-cdiライブラリの記事を探す
    * [](#拡張機能追加)


# MyBatisCDIExtension
cdi大好きだから、そういう風に振る舞う拡張機能書いた。
@InjectMapperを付与されてるフィールドを対象に自動的にSqlSession#getMapperからインスタンス取得を設定してる。
```なんかのテストクラス.java
@InjectMapper
XXXDao xxxDao;
```

```org/mybatis/cdi/junit/jupiter/InjectMapper.java
package org.mybatis.cdi.junit.jupiter;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InjectMapper {

}

```
```org/apache/ibatis/junit/jupiter/MyBatisExtension.java
package org.mybatis.cdi.junit.jupiter;

import java.lang.reflect.Field;
import java.util.Objects;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.util.ConfigUtil;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.support.AnnotationSupport;

public class MyBatisCDIExtension
        implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback {

    private final ConfigUtil configUtil = ConfigUtil.load("mybatis-config.xml");
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        if (Objects.isNull(this.sqlSessionFactory)) {
            this.sqlSessionFactory = this.configUtil.getDefault();
        }

        this.sqlSession = this.sqlSessionFactory.openSession();
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        Class<?> targetClass = context.getRequiredTestClass();

        for (Field field : AnnotationSupport.findAnnotatedFields(targetClass, InjectMapper.class)) {
            field.trySetAccessible();
            field.set(context.getRequiredTestInstance(),
                    this.sqlSession.getMapper(field.getType()));
        }
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        if (Objects.nonNull(this.sqlSession)) {
            this.sqlSession.close();
        }
    }
}
```