Kotlin Test Tutorial
📘 1. 데이터베이스에 저장하고, 해당 값을 불러와서 조회해야할 때
✅ 기본 예시 1: save + findById
@Test
fun `기본 - 저장 후 조회`() {
val repo = mockk<UserRepository>()
val user = User(id = 1L, name = "홍길동")
every { repo.save(any()) } returns user
every { repo.findById(1L) } returns user
val saved = repo.save(user)
val found = repo.findById(1L)
assertEquals("홍길동", found.name) // 저장된 값 검증
}
✅ 기본 예시 2: saveAll + findAll
@Test
fun `기본 - 여러건 저장 후 전체 조회`() {
val repo = mockk<UserRepository>()
val users = listOf(User(1, "A"), User(2, "B"))
every { repo.saveAll(any()) } returns users
every { repo.findAll() } returns users
repo.saveAll(users)
val found = repo.findAll()
assertEquals(2, found.size)
}
💡 실전 예시 1: IdentifyHistory 저장 후 조회
@Test
fun `실전 - 본인인증 저장 후 결과 조회`() {
val history = IdentifyHistory(...).apply { id = 1L }
every { identifyHistoryRepository.saveAndFlush(any()) } returns history
every { identifyHistoryRepository.getResult(1L) } returns IdentifyResultResponse(1L, "0000", "성공")
val id = identifyService.saveIdentify(request, "127.0.0.1")
val result = identifyService.getResult(id)
assertEquals("0000", result.resultCode)
}
💡 실전 예시 2: save 후 count 조회
@Test
fun `실전 - 요청 저장 후 count 조회`() {
val history = IdentifyHistory(...).apply { id = 10L }
every { identifyHistoryRepository.save(any()) } returns history
every {
identifyHistoryRepository.countByMobileAndTypeAndCreatedAtBetween(any(), any(), any(), any())
} returns 1
identifyService.savePreIdentify(request, "127.0.0.1")
val count = identifyService.getRequestCount(countRequest)
assertEquals(1, count)
}
📘 2. @Transactional 어노테이션이 붙어있을 때
단위 테스트에서는 트랜잭션이 크게 의미가 없지만, 통합 테스트에서는 실제 DB 롤백/커밋 여부가 중요합니다.
✅ 기본 예시 1: 단위 테스트에서 무시
@Service
class MyService(val repo: UserRepository) {
@Transactional
fun saveUser(user: User) = repo.save(user)
}
@Test
fun `기본 - Transactional은 단위테스트에 영향 없음`() {
val repo = mockk<UserRepository>()
val service = MyService(repo)
every { repo.save(any()) } returns User(1, "홍길동")
val result = service.saveUser(User(0, "홍길동"))
assertEquals(1, result.id)
}
✅ 기본 예시 2: 통합 테스트에서 롤백 확인
@SpringBootTest
@Transactional
class TransactionTest(@Autowired val repo: UserRepository) {
@Test
fun `기본 - 저장 후 롤백`() {
repo.save(User(0, "홍길동"))
val count = repo.count()
assertEquals(1, count)
// 테스트 끝나면 롤백 -> DB에 데이터 없음
}
}
💡 실전 예시 1: IdentifyService saveIdentify
@Test
fun `실전 - saveIdentify는 트랜잭션 내에서 저장`() {
val history = IdentifyHistory(...).apply { id = 1L }
every { identifyHistoryRepository.saveAndFlush(any()) } returns history
val id = identifyService.saveIdentify(request, "127.0.0.1")
assertEquals(1L, id)
}
💡 실전 예시 2: savePreIdentify (트랜잭션 읽기 전용 아님)
@Test
fun `실전 - savePreIdentify도 트랜잭션 내 저장`() {
every { identifyHistoryRepository.save(any()) } returns IdentifyHistory(...)
identifyService.savePreIdentify(request, "127.0.0.1")
verify { identifyHistoryRepository.save(any()) }
}
📘 3. when CALLED -> return X
(MockK의 every { ... } returns ...
구문)
✅ 기본 예시 1
every { repo.findById(1L) } returns User(1, "홍길동")
✅ 기본 예시 2
every { repo.count() } returns 5
💡 실전 예시 1
every { identifyHistoryRepository.getResult(1L) } returns IdentifyResultResponse(1L, "0000", "성공")
💡 실전 예시 2
every { identifyHistoryRepository.findVerifyValidateInfo("tx123") } returns IdentifyVerifyValidationInfoDto("ci", "홍길동", "19900101")
📘 4. Slot
✅ 기본 예시 1
val slot = slot<User>()
every { repo.save(capture(slot)) } returns User(1, "홍길동")
repo.save(User(0, "홍길동"))
assertEquals("홍길동", slot.captured.name)
✅ 기본 예시 2
val slot = slot<String>()
every { repo.findByName(capture(slot)) } returns User(1, "A")
repo.findByName("AAA")
assertEquals("AAA", slot.captured)
💡 실전 예시 1
val slot = slot<IdentifyHistory>()
every { identifyHistoryRepository.save(capture(slot)) } returns IdentifyHistory(...)
identifyService.savePreIdentify(request, "127.0.0.1")
assertEquals("홍길동", slot.captured.name)
💡 실전 예시 2
val slot = slot<IdentifyHistory>()
every { identifyHistoryRepository.saveAndFlush(capture(slot)) } returns IdentifyHistory(...).apply { id = 1L }
identifyService.saveIdentify(request, "127.0.0.1")
assertEquals(IdentifyType.VERIFY, slot.captured.type)
📘 5. mockk()
✅ 기본 예시 1
val repo = mockk<UserRepository>()
✅ 기본 예시 2
val user = mockk<User>()
every { user.name } returns "홍길동"
💡 실전 예시 1
every { identifyHistoryRepository.save(any()) } returns mockk()
💡 실전 예시 2
val mockHistory: IdentifyHistory = mockk()
every { mockHistory.id } returns 1L
every { identifyHistoryRepository.saveAndFlush(any()) } returns mockHistory
📘 6. verify
✅ 기본 예시 1
every { repo.save(any()) } returns User(1, "홍길동")
repo.save(User(0, "홍길동"))
verify { repo.save(any()) }
✅ 기본 예시 2
every { repo.deleteById(1L) } returns Unit
repo.deleteById(1L)
verify { repo.deleteById(1L) }
💡 실전 예시 1
every { identifyHistoryRepository.save(any()) } returns IdentifyHistory(...)
identifyService.savePreIdentify(request, "127.0.0.1")
verify { identifyHistoryRepository.save(any()) }
💡 실전 예시 2
every { identifyHistoryRepository.countByMobileAndTypeAndCreatedAtBetween(any(), any(), any(), any()) } returns 3
identifyService.getRequestCount(countRequest)
verify {
identifyHistoryRepository.countByMobileAndTypeAndCreatedAtBetween(
countRequest.mobile,
IdentifyType.REQUEST,
countRequest.from,
countRequest.to
)
}
📘 7. @BeforeEach / @AfterEach
테스트 실행 전/후에 반복적으로 실행해야 하는 코드를 넣을 때 사용합니다.
✅ 기본 예시 1
@BeforeEach
fun setup() {
println("테스트 시작 전 실행")
}
✅ 기본 예시 2
@AfterEach
fun cleanup() {
println("테스트 끝난 후 실행")
}
💡 실전 예시 1
@BeforeEach
fun initMocks() {
clearAllMocks() // MockK에서 이전 테스트 설정 초기화
}
💡 실전 예시 2
@AfterEach
fun clear() {
database.cleanTables() // 실제 통합 테스트에서 테이블 초기화
}
📘 8. assert 계열 (검증)
테스트의 기대값 vs 실제값을 비교할 때 사용합니다.
✅ 기본 예시 1
assertEquals(5, 2 + 3)
✅ 기본 예시 2
assertTrue("kotlin".startsWith("kot"))
💡 실전 예시 1
val result = identifyService.getResult(1L)
assertNotNull(result)
💡 실전 예시 2
val count = identifyService.getRequestCount(countRequest)
assertEquals(3, count, "요청 횟수는 3이어야 한다")
📘 9. assertThrows (예외 검증)
예외가 발생하는 상황을 테스트할 때 사용합니다.
✅ 기본 예시 1
assertThrows<IllegalArgumentException> {
throw IllegalArgumentException("잘못된 값")
}
✅ 기본 예시 2
assertThrows<NullPointerException> {
val str: String? = null
str!!.length
}
💡 실전 예시 1
assertThrows<InternalServerException> {
identifyService.saveIdentify(request, "127.0.0.1") // id=null 반환되면 예외 발생
}
💡 실전 예시 2
assertThrows<NoSuchElementException> {
identifyHistoryRepository.findById(999L).get()
}
📘 10. any()
MockK에서 모든 값 허용 매처입니다.
✅ 기본 예시 1
every { repo.findById(any()) } returns User(1, "홍길동")
✅ 기본 예시 2
every { repo.save(any()) } returns User(2, "이몽룡")
💡 실전 예시 1
every { identifyHistoryRepository.saveAndFlush(any()) } returns IdentifyHistory(...).apply { id = 1L }
💡 실전 예시 2
verify { identifyHistoryRepository.countByMobileAndTypeAndCreatedAtBetween(any(), any(), any(), any()) }
📘 11. coEvery / coVerify
코루틴 suspend
함수를 테스트할 때 사용합니다.
✅ 기본 예시 1
coEvery { repo.findUser(any()) } returns User(1, "홍길동")
✅ 기본 예시 2
coVerify { repo.saveUser(any()) }
💡 실전 예시 1
coEvery { identifyAsyncClient.verify(any()) } returns true
💡 실전 예시 2
coVerify { identifyAsyncClient.verify(request) }
📘 12. parameterizedTest (파라미터화 테스트)
여러 입력값으로 반복 테스트할 때 유용합니다.
✅ 기본 예시 1
@ParameterizedTest
@ValueSource(strings = ["A", "B", "C"])
fun `문자열 길이 테스트`(input: String) {
assertTrue(input.length == 1)
}
✅ 기본 예시 2
@ParameterizedTest
@ValueSource(ints = [1, 2, 3])
fun `정수 양수 테스트`(num: Int) {
assertTrue(num > 0)
}
💡 실전 예시 1
@ParameterizedTest
@CsvSource(
"01012345678,true",
"010, false"
)
fun `전화번호 유효성 검사`(mobile: String, expected: Boolean) {
assertEquals(expected, mobile.length == 11)
}
💡 실전 예시 2
@ParameterizedTest
@CsvSource(
"홍길동, 19900101",
"이몽룡, 19950505"
)
fun `이름과 생년월일 매핑 테스트`(name: String, birth: String) {
val dto = IdentifyVerifyValidationInfoDto("ci", name, birth)
assertEquals(birth, dto.birth)
}
📘 13. @Mockk, @InjectMockKs
Mock 객체와 주입 객체를 간단히 설정할 수 있습니다.
✅ 기본 예시 1
@Mockk
lateinit var repo: UserRepository
✅ 기본 예시 2
@InjectMockKs
lateinit var service: UserService
💡 실전 예시 1
@Mockk
lateinit var identifyHistoryRepository: IdentifyHistoryRepository
@InjectMockKs
lateinit var identifyService: IdentifyService
💡 실전 예시 2
@BeforeEach
fun setUp() = MockKAnnotations.init(this)
📘 14. @SpringBootTest / @DataJpaTest
스프링 통합 테스트용 어노테이션입니다.
✅ 기본 예시 1
@SpringBootTest
class IntegrationTest
✅ 기본 예시 2
@DataJpaTest
class RepositoryTest
💡 실전 예시 1
@SpringBootTest
@Transactional
class IdentifyServiceIntegrationTest(
@Autowired val identifyService: IdentifyService
)
💡 실전 예시 2
@DataJpaTest
class IdentifyHistoryRepositoryTest(
@Autowired val identifyHistoryRepository: IdentifyHistoryRepository
)
📘 15. Capturing multiple values
Slot은 1개만 저장되지만, mutableListOf<>()
로 여러 값도 캡처 가능합니다.
✅ 기본 예시 1
val list = mutableListOf<User>()
every { repo.save(capture(list)) } answers { list.last() }
✅ 기본 예시 2
repo.save(User(1, "A"))
repo.save(User(2, "B"))
assertEquals(2, list.size)
💡 실전 예시 1
val captured = mutableListOf<IdentifyHistory>()
every { identifyHistoryRepository.save(capture(captured)) } answers { captured.last() }
identifyService.savePreIdentify(request1, "ip")
identifyService.savePreIdentify(request2, "ip")
assertEquals(2, captured.size)
💡 실전 예시 2
val captured = mutableListOf<IdentifyHistory>()
every { identifyHistoryRepository.saveAndFlush(capture(captured)) } answers { captured.last() }
identifyService.saveIdentify(request, "ip")
assertEquals(IdentifyType.VERIFY, captured.first().type)
📘 16. answers (동적으로 리턴값 만들기)
정적인 returns
대신, 호출될 때 전달된 인자에 따라 동적으로 결과를 반환.
✅ 기본 예시 1
every { repo.findById(any()) } answers { User(firstArg(), "이름") }
✅ 기본 예시 2
every { repo.count() } answers { 10 + 5 }
💡 실전 예시 1
every { identifyHistoryRepository.save(any()) } answers {
val history = firstArg<IdentifyHistory>()
history.apply { id = 99L }
}
💡 실전 예시 2
every { identifyHistoryRepository.findVerifyValidateInfo(any()) } answers {
IdentifyVerifyValidationInfoDto("ci", "홍길동", "19900101")
}
📘 17. relaxed mock (필요한 부분만 스텁)
mockk(relaxed = true)
→ 기본 리턴값을 자동으로 만들어줌.
✅ 기본 예시 1
val repo = mockk<UserRepository>(relaxed = true)
✅ 기본 예시 2
val service = mockk<MyService>(relaxed = true)
💡 실전 예시 1
val repo = mockk<IdentifyHistoryRepository>(relaxed = true)
identifyService = IdentifyService(repo)
💡 실전 예시 2
val mockHistory = mockk<IdentifyHistory>(relaxed = true)
every { mockHistory.id } returns 1L
📘 18. clearMocks / clearAllMocks
테스트 간 mock 설정 초기화.
✅ 기본 예시 1
clearAllMocks()
✅ 기본 예시 2
clearMocks(repo)
💡 실전 예시 1
@BeforeEach
fun setup() {
clearAllMocks()
}
💡 실전 예시 2
@AfterEach
fun cleanup() {
clearMocks(identifyHistoryRepository)
}
📘 19. confirmVerified / excludeRecords
모든 mock이 기대대로 호출됐는지 확인.
✅ 기본 예시 1
confirmVerified(repo)
✅ 기본 예시 2
excludeRecords { repo.toString() }
💡 실전 예시 1
verify { identifyHistoryRepository.save(any()) }
confirmVerified(identifyHistoryRepository)
💡 실전 예시 2
excludeRecords { identifyHistoryRepository.toString() }
📘 20. order / sequence 검증
메서드 호출 순서를 검증.
✅ 기본 예시 1
verifyOrder {
repo.save(any())
repo.findAll()
}
✅ 기본 예시 2
verifySequence {
repo.save(any())
repo.delete(any())
}
💡 실전 예시 1
verifyOrder {
identifyHistoryRepository.save(any())
identifyHistoryRepository.saveAndFlush(any())
}
💡 실전 예시 2
verifySequence {
identifyHistoryRepository.save(any())
identifyHistoryRepository.getResult(any())
}
📘 21. timeout (비동기 동작 검증)
호출이 일정 시간 내 일어났는지 검증.
✅ 기본 예시 1
verify(timeout = 1000) { repo.save(any()) }
✅ 기본 예시 2
verify(atLeast = 1, timeout = 500) { repo.findAll() }
💡 실전 예시 1
verify(timeout = 2000) { identifyHistoryRepository.save(any()) }
💡 실전 예시 2
verify(atLeast = 1, timeout = 1000) { identifyHistoryRepository.countByMobileAndTypeAndCreatedAtBetween(any(), any(), any(), any()) }
📘 22. spyK (부분 모킹)
기존 객체 일부만 모킹.
✅ 기본 예시 1
val list = spyk(mutableListOf<String>())
list.add("A")
verify { list.add("A") }
✅ 기본 예시 2
val user = spyk(User(1, "홍길동"))
every { user.name } returns "이몽룡"
💡 실전 예시 1
val service = spyk(IdentifyService(identifyHistoryRepository))
💡 실전 예시 2
val history = spyk(IdentifyHistory(...))
every { history.name } returns "SpyTest"
📘 23. @TestInstance(TestInstance.Lifecycle.PER_CLASS)
테스트 인스턴스를 클래스 단위로 공유.
✅ 기본 예시 1
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest
✅ 기본 예시 2
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RepoTest
💡 실전 예시 1
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IdentifyServiceTest {
// beforeAll, afterAll 가능
}
💡 실전 예시 2
@BeforeAll
fun initAll() { println("모든 테스트 시작 전 1회 실행") }
📘 24. SpringExtension (JUnit5 + Spring 연동)
JUnit5에서 스프링 컨텍스트 사용.
✅ 기본 예시 1
@ExtendWith(SpringExtension::class)
class SpringTest
✅ 기본 예시 2
@ExtendWith(SpringExtension::class)
@SpringBootTest
class IntegrationTest
💡 실전 예시 1
@ExtendWith(SpringExtension::class)
@DataJpaTest
class IdentifyRepositoryTest
💡 실전 예시 2
@ExtendWith(SpringExtension::class)
@SpringBootTest
class IdentifyServiceIntegrationTest
📘 25. Testcontainers (실제 DB 띄워서 통합 테스트)
실제 DB 환경을 도커로 실행.
✅ 기본 예시 1
@Testcontainers
class ContainerTest {
@Container
val postgres = PostgreSQLContainer("postgres:15")
}
✅ 기본 예시 2
@Testcontainers
class RedisTest {
@Container
val redis = GenericContainer("redis:7").withExposedPorts(6379)
}
💡 실전 예시 1
@Testcontainers
class IdentifyRepositoryTest {
@Container
val mysql = MySQLContainer("mysql:8")
}
💡 실전 예시 2
@Testcontainers
class IdentifyServiceIntegrationTest {
@Container
val cassandra = CassandraContainer("cassandra:4")
}
📘 26. Tagging / Filtering (테스트 그룹핑)
✅ 기본 예시 1
@Tag("fast")
@Test
fun fastTest() {}
✅ 기본 예시 2
@Tag("slow")
@Test
fun slowTest() {}
💡 실전 예시 1
@Tag("repository")
@Test
fun repoTest() {}
💡 실전 예시 2
@Tag("integration")
@Test
fun integrationTest() {}
📘 27. Nested Tests (중첩 구조 테스트)
✅ 기본 예시 1
@Nested
inner class SaveTests {
@Test fun saveSuccess() {}
}
✅ 기본 예시 2
@Nested
inner class FindTests {
@Test fun findSuccess() {}
}
💡 실전 예시 1
@Nested
inner class IdentifySaveTests {
@Test fun saveIdentifySuccess() {}
}
💡 실전 예시 2
@Nested
inner class IdentifyQueryTests {
@Test fun getRequestCountTest() {}
}
Last updated