1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-03-04

アプリをテストしたくなった

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();
        }
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?