Skip to content

単体テスト作成

単体テストの書き方およびカバレッジの確認方法を説明します。

cf. https://spring.pleiades.io/spring-boot/reference/testing/spring-boot-applications.html

事前準備

下記設定を追記します:

  • test/resources/application-test.properties
# エンティティクラスからスキーマを自動生成しない
spring.jpa.hibernate.ddl-auto=none
# h2db接続設定
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
  • pom.xml
        <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
  • XxxApplicationTests.java
// test配下の自動生成済みテストクラスに下記を追記
@ActiveProfiles("test")

作成手順

前提

下記テーブルに対してデータを取得 / 登録する API を想定しています:

カラム名 属性
name VARCHAR(8) PRIMARY KEY
password VARCHAR(32)
age INT

controller テスト作成

package nob.example.easyapp.controller;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import com.fasterxml.jackson.databind.ObjectMapper;

import nob.example.easyapp.controller.model.LoginRequest;
import nob.example.easyapp.controller.model.LoginResponse;
import nob.example.easyapp.controller.model.MeRequest;
import nob.example.easyapp.controller.model.MeResponse;
import nob.example.easyapp.service.AuthService;
import nob.example.easyapp.service.model.LoginInModel;
import nob.example.easyapp.service.model.LoginOutModel;
import nob.example.easyapp.service.model.MeInModel;
import nob.example.easyapp.service.model.MeOutModel;

/**
 * AuthControllerImplのテストクラスです。
 *
 * @author nob
 */
@WebMvcTest
public class AuthControllerImplTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockitoBean
    private AuthService authService;

    /**
     * loginのテスト 正常系
     */
    @Test
    void test_login_success() {

        // リクエストの作成
        LoginRequest request = new LoginRequest("nob", "passwd");

        // serviceのモック化
        Mockito.when(authService.login(new LoginInModel(request.getName(), request.getPassword())))
                .thenReturn(new LoginOutModel(true));

        try {
            // API呼び出し
            MvcResult result = mockMvc.perform(post("/api/v1/login")
                    .content(objectMapper.writeValueAsString(request))
                    .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andReturn();
            // 結果のassert
            assertThat(result.getResponse().getContentAsString())
                    .isEqualTo(objectMapper.writeValueAsString(new LoginResponse(true)));
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }

    /**
     * meのテスト 正常系
     */
    @Test
    void test_me_success() {

        // リクエストの作成
        MeRequest request = new MeRequest("nob");

        // serviceのモック化
        Mockito.when(authService.me(new MeInModel(request.getName()))).thenReturn(new MeOutModel("nob", 13));

        try {
            // API呼び出し
            MvcResult result = mockMvc.perform(get("/api/v1/me")
                    .queryParam("name", request.getName())
                    .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andReturn();
            // 結果のassert
            assertThat(result.getResponse().getContentAsString())
                    .isEqualTo(objectMapper.writeValueAsString(new MeResponse("nob", 13)));
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }
}

service テスト作成

package nob.example.easyapp.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

import nob.example.easyapp.repository.UsersRepository;
import nob.example.easyapp.repository.entity.Users;
import nob.example.easyapp.service.model.LoginInModel;
import nob.example.easyapp.service.model.LoginOutModel;
import nob.example.easyapp.service.model.MeInModel;
import nob.example.easyapp.service.model.MeOutModel;

/**
 * AuthServiceImplのテストクラスです。
 *
 * @author nob
 */
@SpringBootTest
@ActiveProfiles("test")
public class AuthServiceImplTest {

    @Autowired
    private AuthService authService;

    @MockitoBean
    private UsersRepository usersRepository;

    /**
     * loginのテスト 正常系
     */
    @Test
    void test_login_success() {

        // 入力値の作成
        LoginInModel inModel = new LoginInModel("nob", "passwd");

        // repositoryのモック化
        Mockito.when(usersRepository.findByName("nob")).thenReturn(new Users("nob", "passwd", 13));

        try {
            // service呼び出し
            LoginOutModel outModel = authService.login(inModel);
            // 結果のassert
            assertThat(outModel.isValid()).isTrue();
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }

    /**
     * meのテスト 正常系
     */
    @Test
    void test_me_success() {

        // 入力値の作成
        MeInModel inModel = new MeInModel("nob");

        // repositoryのモック化
        Mockito.when(usersRepository.findByName("nob")).thenReturn(new Users("nob", "passwd", 13));

        try {
            // service呼び出し
            MeOutModel outModel = authService.me(inModel);
            // 結果のassert
            assertThat(outModel.getName()).isEqualTo("nob");
            assertThat(outModel.getAge()).isEqualTo(13);
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }
}

repository テスト作成

テストデータ準備

test/resources/repository/users 配下にテストデータ投入向けの SQL を用意します:

  • schema.sql
CREATE TABLE IF NOT EXISTS users(
    name VARCHAR(8) PRIMARY KEY
    , password VARCHAR(32)
    , age INT
);
  • data.sql
INSERT INTO users (
    name
    , password
    , age
) VALUES (
    'nob'
    , 'passwd'
    , 13
);

テストケース作成

package nob.example.easyapp.repository;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;

import nob.example.easyapp.repository.entity.Users;

/**
 * UsersRepositoryのテストクラスです。
 *
 * @author nob
 */
@DataJpaTest
@ActiveProfiles("test")
@TestPropertySource(properties = {
        "spring.sql.init.schema-locations=classpath:/repository/users/schema.sql",
        "spring.sql.init.data-locations=classpath:/repository/users/data.sql"
})
public class UsersRepositoryTest {

    @Autowired
    private UsersRepository usersRepository;

    /**
     * findByNameのテスト 正常系
     */
    @Test
    void test_findByName() {

        try {
            Users users = usersRepository.findByName("nob");
            assertThat(users.getName()).isEqualTo("nob");
            assertThat(users.getPassword()).isEqualTo("passwd");
            assertThat(users.getAge()).isEqualTo(13);
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }
}

テスト起動

# 特定のテストクラスを実行する場合
./mvnw test -Dtest=AuthControllerImplTest

# 全てのテストクラスを実行する場合
./mvnw test

カバレッジ出力

cf. https://www.jacoco.org/jacoco/trunk/doc/maven.html

設定

下記設定を追記します:

  • pom.xml
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.14</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <reportSets>
                    <reportSet>
                        <reports>
                        <!-- select non-aggregate reports -->
                        <report>report</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>

カバレッジレポート作成

  • カバレッジレポートを target/site 配下に出力します:
./mvnw test jacoco:report