単体テスト作成
単体テストの書き方およびカバレッジの確認方法を説明します。
事前準備
下記設定を追記します:
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