Skip to content

単体テスト作成

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

事前準備

下記設定を追記します:

  • 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>
        <!-- Source: https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin -->
        <dependency>
            <groupId>org.mockito.kotlin</groupId>
            <artifactId>mockito-kotlin</artifactId>
            <version>6.3.0</version>
            <scope>test</scope>
        </dependency>
  • EasyappApplicationTests.kt
package nob.example.easyapp

import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles

@SpringBootTest
@ActiveProfiles("test") // データソースをH2DBとするためテスト向けプロパティを読み込ませる
class EasyappApplicationTests {

    @Test
    fun contextLoads() {
    }

}

作成手順

controllerテスト作成

  • controller/impl/AuthControllerImplTest.kt
package nob.example.easyapp.controller.impl

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
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.webmvc.test.autoconfigure.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.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import tools.jackson.databind.ObjectMapper

/**
 * AuthControllerImplのテストクラスです。
 */
@WebMvcTest(AuthControllerImpl::class)
class AuthControllerImplTest {

    @Autowired
    lateinit var mockMvc: MockMvc

    @MockitoBean
    lateinit var authService: AuthService

    var objectMapper: ObjectMapper = ObjectMapper()

    /**
     * loginのテスト 正常系
     */
    @Test
    fun testLoginSuccess() {

        // リクエスト作成
        val req = LoginRequest("nob", "passwd")

        // serviceのモック化
        whenever(authService.login(LoginInModel(req.name, req.password))).thenReturn(LoginOutModel(true))

        // API呼び出し
        val result = mockMvc.perform(
            post("/api/v1/login")
                .content(objectMapper.writeValueAsString(req))
                .contentType(MediaType.APPLICATION_JSON)
        )
            .andExpect(status().isOk())
            .andReturn()
        // 結果の検証
        assertThat(result.response.contentAsString).isEqualTo(objectMapper.writeValueAsString(LoginResponse(true)))
    }

    /**
     * meのテスト 正常系
     */
    @Test
    fun testMeSuccess() {

        // リクエスト作成
        val req = MeRequest("nob")

        // serviceのモック化
        whenever(authService.me(MeInModel(req.name))).thenReturn(MeOutModel("nob", 13))

        // API呼び出し
        val result = mockMvc.perform(
            get("/api/v1/me")
                .queryParam("name", req.name)
                .contentType(MediaType.APPLICATION_JSON)
        )
            .andExpect(status().isOk())
            .andReturn()
        // 結果の検証
        assertThat(result.response.contentAsString).isEqualTo(objectMapper.writeValueAsString(MeResponse("nob", 13)))
    }
}

serviceテスト作成

  • service/impl/AuthServiceImplTest.kt
package nob.example.easyapp.service.impl

import nob.example.easyapp.domain.entity.Users
import nob.example.easyapp.repository.UsersRepository
import nob.example.easyapp.service.model.LoginInModel
import nob.example.easyapp.service.model.MeInModel
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.whenever
import java.util.*

/**
 * AuthServiceImplのテストクラスです。
 */
@ExtendWith(MockitoExtension::class)
class AuthServiceImplTest {

    @InjectMocks
    lateinit var authService: AuthServiceImpl

    @Mock
    lateinit var usersRepository: UsersRepository

    /**
     * loginのテスト 正常系
     */
    @Test
    fun testLoginSuccess() {

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

        // repositoryのモック化
        whenever(usersRepository.findByName(inModel.name)).thenReturn(Optional.of(Users("nob", "passwd", 13)))

        // service呼び出し
        val outModel = authService.login(inModel)
        // 結果のassert
        assertThat(outModel.valid).isTrue()
    }

    /**
     * meのテスト 正常系
     */
    @Test
    fun testMeSuccess() {

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

        // repositoryのモック化
        whenever(usersRepository.findByName(inModel.name)).thenReturn(Optional.of(Users("nob", "passwd", 13)))

        // service呼び出し
        val outModel = authService.me(inModel)
        // 結果の検証
        assertThat(outModel.name).isEqualTo("nob")
        assertThat(outModel.age).isEqualTo(13)
    }
}

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
);

テストケース作成

  • repository/UsersRepositoryTest.kt
package nob.example.easyapp.repository

import nob.example.easyapp.domain.entity.Users
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.TestPropertySource

/**
 * UsersRepositoryのテストクラスです。
 */
@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"
    ]
)
class UsersRepositoryTest {

    @Autowired
    lateinit var usersRepository: UsersRepository

    /**
     * findByNameのテスト 正常系
     */
    @Test
    fun testFindByNameSuccess() {

        // repository呼び出し
        val optUsers = usersRepository.findByName("nob")
        // 結果のassert
        assertThat(optUsers.isPresent).isTrue()
        assertThat(optUsers.get()).usingRecursiveComparison().isEqualTo(Users("nob", "passwd", 13))
    }
}

テスト起動

# 特定のテストクラスを実行する場合
./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