kotlin

[Kotest + Mockk] 자주 활용되는 기능 모음 (justRun, verify, spyk, shouldThrow)

juhi 2024. 5. 31. 02:00

아래는 kotest + mockk 으로 코틀린 테스트 코드를 작성하면서 내가 자주 사용하는 부분들을 정리해봤다. 

return 값 없는 메소드(Unit) mocking 하기

mock 클래스에서 응답이 없는 메소드더라고 mocking해주지 않으면 no answer 에러가 난다. 이럴 때에는 그냥 실행한다라는 의미의 mocking이 필요하다.

justRun { testService.runMethod() }
every { testService.runMethod() } just runs
every { testService.runMethod() } returns Unit

위 3가지는 모두 같은 의미이다.

주의) 리턴 값이 있지만 사용하지 않는 메소드의 경우 위와 같이 mocking하면 ClassCastException 이 발생한다.

class MemberService (private val memberRepository: MemeberRepository) {
    fun saveMember(member: Member) {
        ...
        memberRepository.save(member)
    }
}
// saveMember에 대한 테스트 시, memberRepository.save()의 리턴값을 사용하지 않는다고 아래와 같이 mocking하면 Exception 발생 
justRun { memberRepository.save(member) }
// java.lang.ClassCastException: class kotlin.Unit cannot be cast to class Member

메소드 실행 횟수 검증

verify를 통해서 호출 횟수를 테스트할 수 있다.

verify(exactly = $호출횟수) { memberRepository.save(member) }

아래는 호출하지 않음을 테스트하는 예제이다

import io.mockk.Called
import io.mockk.verify

class MemberServiceTest : DescribeSpec({
	describe("saveMember 메소드는") {
            context("사용하지 않는 member의 경우") {
                it("저장하지 않는다") {
                    val member = Member.created().apply { use = false } 

                    memberService.saveMember(member)

                    // 호출하지 않음을 테스트 1
                    verify { memberRepository.save(member) wasNot Called }

                    // 호출하지 않음을 테스트 2
                    verify(exactly = 0) { memberRepository.save(member) }
                }
            }
	}
})

주의) 같은 메소드에 대한 테스트 케이스가 여러개일 경우 mock clear를 해주지 않으면 앞 테스트 케이스들에서의 호출 횟수가 쌓여서 정상적인 호출 횟수 비교가 안된다. 아래처럼 clearAllMocks()를 afterEach 혹은 beforeEach로 실행해줘야 한다.

afterEach { clearAllMocks() }

메소드 실행 중 사용된 인자를 캡쳐하여 검증하기 - slot

메소드 실행 중에 사용된 인자를 캡쳐해서 그 때의 값을 테스트할 수 있다. 예를 들어 아래 saveMember의 메소드에서 repository에 save하기 전에 받아온 member에 대한 변경이 있었다고 하면, 제대로 변경돼서 repository.save의 인자로 넘어갔는지를 테스트 해야한다.

class MemberService (private val memberRepository: MemeberRepository) {
    fun saveMember(member: Member) {
        // 인자로 받아온 member를 변경하는 로직
        memberRepository.save(member)
    }
}

이 경우 Slot객체를 만들고, mocking 시 capture로 넘겨준다. 이후 Slot 객체의 captured로 mocking한 메소드가 실행될 때 넘겼던 인자의 값들을 확인할 수 있다.

class MemberServiceTest : DescribeSpec({
  describe("saveMember 메소드는") {
      context("~할 경우") {
          it("member의 상태 값을 sleeper로 변경해서 저장해야한다.") {
              val memberSlot = slot<Member>()

              every { memberRepository.save(capture(memberSlot)) } returns member

              memberService.saveMember(member) sholudBe member

              // saveMember 메소드 내부에서 memberRepository.save에 넘긴 member가 capture된다.
              memberSlot.captured.status sholudBe MemberStatus.SLEEPER
          }
      }
  }
})

예외 테스트

직접 try catch로 캐치한 exception의 내부 필드를 비교할 수도 있지만 아래처럼 shouldThrow, shouldThrowExactly, shouldThrowAny를 활용하면 좀 더 kotest 스럽게(?) 테스트가 가능하다.

활용 예시

shouldThrowExactly<ResponseStatusException> {
    memberService.saveMember(member)
}.should { e ->
    e.status shouldBe HttpStatus.BAD_REQUEST
    e.message shouldBe "잘못된 요청입니다."
}

- shouldThrowExactly: throw 되는 Exception의 타입까지 검증. FileNotFoundException의 부모클래스인 IOException를 던지는 것도 허용하지 않는다.

 

val exception = shouldThrowExactly<FileNotFoundException> {
  // test here
}

- shouldThrow: shouldThrowExactly와 마찬가지로 타입까지 검증하나,부모클래스도 허용

val exception = shouldThrowExactly<FileNotFoundException> {
  // test here
}

- shouldThrowAny: 예외 타입에 관계없이, 예외를 던지는 여부만을 테스트할 때

val exception = shouldThrowAny { // test here can throw any type of Throwable! }

spky()

mock 객체는 mockk()를 이용해서 선언하는데, 만약 spyk()를 사용한다면 일부만 mocking해서 테스트할 수 있다.

클래스의 일부 메소드만을 mocking 하고, 이외는 그대로 실행까지 테스트하고 싶을 때 아래 memberService같이 mockk 대신 spyk로 생성해주면 된다.

val memberRepository = mockk<MemberRepository>()
val memberService = spyk<MemberService>(memberRepository)

참고


 

MockK

Provides DSL to mock behavior. Built from zero to fit Kotlin language. Supports named parameters, object mocks, coroutines and extension function mocking

mockk.io


 

Kotest | Kotest

Flexible, powerful and elegant kotlin test framework with multiplatform support

kotest.io

반응형