-
Kotest + Mockk 적용기🔖 Kotlin 2025. 5. 14. 15:24
Spring 기반 프로젝트에서 Kotlin을 사용하더라도 기존에 익숙하던 테스트 프레임워크인 JUnit, AssertJ, Mockito 등을 그대로 사용할 수 있습니다.
이번에 Kotlin을 처음 도입한 프로젝트에서 아직 Kotlin의 문법에 익숙하지 않은 상태로 테스트 코드를 작성하다 보니 자연스럽게 기존 Java 스타일의 JUnit 기반 테스트 코드를 작성하게 되었습니다.
import org.junit.jupiter.api.Test import org.assertj.core.api.Assertions.assertThat import org.mockito.Mockito.* class UserServiceTest { private val userRepository = mock(UserRepository::class.java) private val userService = UserService(userRepository) @Test fun `should return user by id`() { val user = User(1, "Beomsic") `when`(userRepository.findById(1)).thenReturn(user) val result = userService.getUserById(1) assertThat(result.name).isEqualTo("Beomsic") verify(userRepository).findById(1) } }
🍃 Kotlin DSL과 테스트코드
Kotlin은 중괄호 기반의 DSL(Domain Specific Language) 지원을 통해 선언적이고 읽기 쉬운 코드 스타일을 지향합니다.
✅ Kotlin DSL의 특징
- Type Safe Builders
- Kotlin Standard Library
- → DSL 패턴 기반 함수가 많음
- Scope Functions (let, also, apply, run, with)
val user = User("Beomsic", 20).apply { age = 21 println(name) }
하지만 기존의 JUnit, AssertJ, Mockito 조합만으로는 이러한 Kotlin DSL의 이점을 테스트 코드에서 적극적으로 활용하기 어렵습니다.
🔥 Kotest & Mockk 도입!!
Kotlin DSL을 적극 활용해 코틀린 스타일로 테스트 코드를 작성할 수 있도록 돕는 프레임워크가 바로 Kotest 와 Mockk 입니다.
📌 Kotest + Mockk 예시
import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk class UserServiceTest : StringSpec({ "should return user by id" { val userRepository = mockk<UserRepository>() val userService = UserService(userRepository) val user = User(1, "Beomsic") every { userRepository.findById(1) } returns user userService.getUserById(1).name shouldBe "Beomsic" } })
🎯 장점 요약
- Assertion: shouldBe, shouldNotBe, shouldContain 등 infix 스타일로 표현
- Mocking: every { ... } returns ... 와 같이 DSL 기반 표현 가능
- Kotlin 문법에 최적화되어 있어 테스트 코드도 자연스럽고 깔끔하게 작성 가능
- 테스트 코드의 가독성 증가 + 일관된 Kotlin 스타일 유지
📚 Kotest란?
Kotest는 Kotlin 언어 특성을 살려 테스트를 더 간단하고 강력하게 작성할 수 있도록 돕는 테스트 프레임워크입니다.
기존의 Java 테스트 프레임워크와 달리 다양한 Spec 스타일과 DSL을 제공합니다.
✅ Kotest 주요 기능
- 다양한 테스트 스타일 지원 (StringSpec, FunSpec, BehaviorSpec, ShouldSpec 등)
- infix assertion: 1 + 2 shouldBe 3
- Exception 테스트: shouldThrow<ExceptionType> { ... }
- Property-based Testing 지원
- Tag 기반 테스트 필터링, Timeout, Retry 등 다양한 부가 기능
🧪 예시 - FunSpec
class FunTest : FunSpec({ test("1 + 2는 3이다") { 1 + 2 shouldBe 3 } })
🧪 예시 - 예외 테스트
test("exception should be thrown") { shouldThrow<IllegalArgumentException> { throw IllegalArgumentException("Invalid input") } }
🧪 예시 - 중첩 테스트
class NestedTest : FunSpec({ context("an nexted test") { test("first test") { 1 + 2 shouldBe 3 } test("second test") { 3 + 4 shouldBe 7 } } })
🧪 예시 - 동적 파라미터 테스트
class KotestTest: FunSpec({ listOf("beo", "sic", "bsc",).forEach { test("$it should have three letter") { it.shouldHaveLength(3) } } })
🐘 Kotest Gradle 설정
build.gradle.kts
dependencies { testImplementation("io.kotest:kotest-runner-junit5:5.9.1") testImplementation("io.kotest:kotest-assertions-core:5.9.1") testImplementation("io.kotest:kotest-framework-engine:5.9.1") } tasks.test { useJUnitPlatform() // Kotest는 JUnit5 엔진 위에서 동작 }
🧰 Spec 스타일 종류
스타일 설명 예시 코드 구조 StringSpec 문자열 기반 테스트 이름 "2 + 2는 4다" { ... } FunSpec test("설명") {} 형식 test("로그인 테스트") { ... } BehaviorSpec BDD 스타일 (Given - When - Then) Given("조건") { When("행동") { Then(...) }} ShouldSpec 자연어 문장 스타일 should("작동해야 한다") { ... } DescribeSpec RSpec 스타일 (describe, it) describe("User") { it("로그인한다") { ... } } AnnotationSpec JUnit과 유사한 어노테이션 기반 @Test fun testLogin() { ... } ExpectSpec expect("설명") {} 형태 expect("회원가입") { ... } FreeSpec 자유로운 구조의 중첩 가능 "회원 기능" - { "가입" { ... } } 🌟 Spec 예제
1️⃣ StringSpec – 간결한 문자열 기반 테스트
class CalculatorTest : StringSpec({ "2 + 2는 4다" { (2 + 2) shouldBe 4 } "리스트는 비어있지 않다" { listOf(1, 2, 3).isEmpty() shouldBe false } })
- 매우 간결함
2️⃣ FunSpec – 가장 익숙한 형태 (JUnit 대체용으로 많이 사용)
class AuthServiceTest : FunSpec({ test("로그인 성공 시 토큰 반환") { val result = login("user", "password") result.token shouldNotBe null } test("비밀번호 틀리면 예외 발생") { shouldThrow<IllegalArgumentException> { login("user", "wrongPassword") } } })
- test()로 명확히 구분 가능, 익숙한 구조
3️⃣ BehaviorSpec – BDD 스타일 테스트 작성
class UserServiceTest : BehaviorSpec({ given("회원가입이 완료된 사용자") { val user = User("test") `when`("로그인 요청을 하면") { val result = login(user) then("로그인에 성공해야 한다") { result shouldBe "SUCCESS" } } } })
- Given-When-Then 구조로 요구사항 기반 테스트에 적합
🧸 Mockk란?
Mockk는 Kotlin 전용 Mocking 프레임워크로 Kotlin의 특성을 고려하여 설계되었습니다.
Mockito의 한계를 보완해 Kotlin 코드에서 더 자연스럽게 Mocking 할 수 있습니다.
✅ 주요 특징
- Kotlin 친화적: object/class/function, coroutines, final class 등도 mock 가능
- DSL 기반 표현 (every { ... } returns ..., verify { ... })
- 코루틴, suspend 함수 지원
- Annotation 기반 설정 가능 (@MockK, @InjectMockKs 등)
✨ 기본 사용 예시
val service = mockk<MyService>() every { service.getName() } returns "Beomsic" service.getName() shouldBe "Beomsic" verify { service.getName() }
🌀 코루틴 지원
val service = mockk<MySuspendService>() coEvery { service.getUser("123") } returns User("Beomsic") runBlocking { service.getUser("123").name shouldBe "Beomsic" }
🔍 Slot 캡처 (인자 검증)
val slot = slot<String>() every { service.sayHello(capture(slot)) } returns "Hello" service.sayHello("Beomsic") slot.captured shouldBe "Beomsic"
🚀 마무리: Kotest와 Mockk 도입을 통해 얻은 점
- Kotlin DSL을 적극 활용하여 테스트 코드가 훨씬 읽기 쉽고 자연스러워졌습니다.
- 비즈니스 로직과 테스트 코드 모두에서 일관된 Kotlin 스타일을 유지할 수 있게 되었습니다.
📚 Ref.
https://techblog.woowahan.com/5825/
https://kotest.io/docs/framework/integrations/mocking.html
https://www.baeldung.com/kotlin/kotest
https://toss.tech/article/kotlin-dsl-restdocs
'🔖 Kotlin' 카테고리의 다른 글
👾 MockK와 Mockito (0) 2025.01.09 😎 Infix Function (0) 2024.12.30 널 안정성 (0) 2024.12.29 🦥 lateinit 과 lazy (0) 2024.12.29 Kotlin Coroutines (0) 2024.12.28