Kotlin을 위해 설계된 Mocking 라이브러리.
Mockito의 Java 한계를 넘어 data class, companion object, coroutine, extension function까지 Mock 가능.
왜 MockK인가?
항목
Mockito
MockK
언어
Java-first
Kotlin-first
final class mock
별도 설정 필요
기본 지원
object / companion mock
불가
mockkObject
Coroutine
별도 라이브러리
coEvery / coVerify
Extension function
불가
mockkStatic
DSL
verbose
간결한 infix DSL
학습 로드맵
단계
주제
목표
1
기본 Mock / Stub
mockk(), every, returns
2
Argument Matcher
any(), eq(), capture(), slot
3
Verification
verify, verifyOrder, verifySequence
4
Spy
spyk(), partial mock
5
Object / Static Mock
mockkObject, mockkStatic
6
Relaxed / Strict Mock
relaxed mock, 기본값 전략
7
Coroutine 지원
coEvery, coVerify, suspend 함수
8
Spring Boot 통합
@MockkBean, SpringMockK
Chapter 1. 기본 Mock & Stub
mockk() — 인터페이스 / 클래스 Mock
// 인터페이스val userRepository = mockk<UserRepository>()// final class (Kotlin 기본값)val emailService = mockk<EmailService>()// 초기화 시 stubbingval config = mockk<AppConfig> { every { timeout } returns 30 every { retryCount } returns 3}
every { } — Stubbing
// 단순 반환every { userRepository.findById(1L) } returns User(id = 1L, name = "김철수")// null 반환every { userRepository.findById(999L) } returns null// 예외 던지기every { userRepository.findById(-1L) } throws IllegalArgumentException("잘못된 ID")// 순서대로 다른 값 반환every { counter.next() } returnsMany listOf(1, 2, 3)// 첫 번째 호출만 예외, 이후 정상every { service.call() } throws RuntimeException() andThen "ok"// 람다로 동적 응답every { service.process(any()) } answers { firstArg<Int>() * 2 }
answers — 동적 응답 (AnswerScope)
every { repository.save(any()) } answers { val user = firstArg<User>() user.copy(id = 100L) // 저장된 엔티티에 ID 부여}every { service.execute(any(), any()) } answers { val (a, b) = args (a as Int) + (b as Int)}
Chapter 2. Argument Matcher
기본 Matcher
every { repo.findById(any()) } returns mockUser // 모든 값every { repo.findById(eq(1L)) } returns specificUser // 정확히 1Levery { repo.findByName(any<String>()) } returns list // 타입 한정every { repo.findAll() } returns emptyList() // 인수 없음
verifySequence { repository.findById(orderId) paymentClient.charge(any()) repository.save(any()) // 이 외의 호출이 있으면 실패}
confirmVerified — 검증되지 않은 호출 확인
verify { service.process(any()) }confirmVerified(service) // 검증하지 않은 메서드 호출이 있으면 실패
wasNot Called
verify { notificationService wasNot Called }verify { auditRepository wasNot Called }
Chapter 4. Spy (부분 Mock)
// 실제 구현 + 일부만 Mockval userService = spyk(UserService(repository))// 특정 메서드만 Overrideevery { userService.sendEmail(any()) } returns Unit // 이메일 전송만 Mock// 나머지는 실제 로직 실행val result = userService.createUser(request) // 실제 로직verify { userService.sendEmail(any()) }
실제 메서드 호출 (callOriginal)
every { spy.expensiveCalculation(any()) } answers { callOriginal() }
mockkConstructor(HttpClient::class)every { anyConstructed<HttpClient>().get(any()) } returns mockResponseval service = ExternalService() // 내부에서 HttpClient() 생성service.fetchData() // Mock HttpClient 사용unmockkConstructor(HttpClient::class)
Chapter 6. Relaxed & Strict Mock
Relaxed Mock — 기본값 자동 반환
// relaxed = true: stubbing 없는 메서드는 기본값 반환val service = mockk<UserService>(relaxed = true)// stubbing 없어도 예외 발생 안 함service.doSomething() // Unit 반환service.getName() // "" 반환service.getCount() // 0 반환service.getUser() // null 반환 (nullable) / 빈 mock (non-null)
relaxedUnitFun — Unit 반환 메서드만 relaxed
val repository = mockk<UserRepository>(relaxedUnitFun = true)// Unit 메서드는 stubbing 없이 호출 가능repository.deleteAll() // OK// 반환값 있는 메서드는 여전히 stubbing 필요every { repository.findById(any()) } returns user
전략 선택
상황
추천 설정
협력 객체 행동 검증이 주 목적
relaxed = true
반환값이 중요한 쿼리 테스트
기본 (strict)
void 메서드만 relaxed
relaxedUnitFun = true
테스트 커버리지 엄격하게
strict + confirmVerified
Chapter 7. Coroutine 지원
coEvery — suspend 함수 Stubbing
class UserServiceTest : FunSpec({ val repository = mockk<UserRepository>() val service = UserService(repository) test("suspend 함수 Mock") { coEvery { repository.findById(1L) } returns User(1L, "김철수") val user = service.getUser(1L) user.name shouldBe "김철수" } test("suspend 함수 예외") { coEvery { repository.findById(999L) } throws NotFoundException("User not found") shouldThrow<NotFoundException> { service.getUser(999L) } }})