Skip to content

単体テスト作成

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

事前準備

下記設定を追記します:

  • test/resources/application-test.properties
shell
# エンティティクラスからスキーマを自動生成しない
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
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
kotlin
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
kotlin
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
kotlin
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

/**
 * 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(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(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
sql
CREATE TABLE IF NOT EXISTS users(
    name VARCHAR(8) PRIMARY KEY
    , password VARCHAR(32)
    , age INT
);
  • data.sql
sql
INSERT INTO users (
    name
    , password
    , age
) VALUES (
    'nob'
    , 'passwd'
    , 13
);

テストケース作成

  • repository/UsersRepositoryTest.kt
kotlin
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 users = usersRepository.findByName("nob")
        // 結果のassert
        assertThat(users).usingRecursiveComparison().isEqualTo(Users("nob", "passwd", 13))
    }
}

テスト起動

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

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

カバレッジ出力

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

設定

下記設定を追記します:

  • pom.xml
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>
xml
	<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配下に出力します:
shell
./mvnw test jacoco:report