Dev./Kotlin

[mockk] private extension function 모킹하기

인쥭 2024. 10. 28. 11:01
반응형

메소드 호출시 내부 동작 과정에서 private extension function을 사용하는 MyClass라는 이름의 클래스가 있다고 하자.
해당 클래스는 인자로 전달 받은 값에 3을 더한 후 문자열로 변환한 값을 반환하는 하나의 메소드만을 공개하고 있으며, 이러한 기능을 Kotest로 테스트한다면 다음과 같다.

class PlaygroundTest : StringSpec({
    "convertToStringAfterAdd3 should return string of value after add 3" {
        // given
        val input = 5
        val myClass = MyClass()

        // when
        val result = myClass.convertToStringAfterAdd3(input)

        // then
        result shouldBe "${input + 3}"
    }
}) {
    class MyClass {
        fun convertToStringAfterAdd3(value: Int): String
            = "${value.add3()}"

        private fun Int.add3(): Int
            = this + 3
    }
}

상술한 바와 같이 MyClass는 내부적으로 add3()라는 Int 확장 함수를 사용하는데, add3() 메소드는 private으로 지정되어 있다.

만약 mockk를 활용하여 이러한 private extension function을 모킹해야 하는 경우가 있다고 하면, 아래와 같이 작성해주면 된다.

class PlaygroundTest : StringSpec({
    "convertToStringAfterAdd3 should return result of Int.add3() as string" {
        // given
        val myClass = spyk(
            objToCopy = MyClass(),
            recordPrivateCalls = true,
        )

        val expected = 42
        // 이럿게
        every { myClass["add3"](ofType(Int::class)) } returns expected

        // when
        val result = myClass.convertToStringAfterAdd3(3)

        // then
        result shouldBe "$expected"
    }
}) {
    class MyClass {
        fun convertToStringAfterAdd3(value: Int): String
            = "${value.add3()}"

        private fun Int.add3(): Int
            = this + 3
    }
}

이는 MyClass가 다음과 같은 java 코드로 변환되기 때문이다.

public static final class MyClass {
   @NotNull
   public final String convertToStringAfterAdd3(int value) {
      return String.valueOf(this.add3(value));
   }

   // 수신 객체가 첫 번째 인자로 전달된다.
   private final int add3(int $this$add3) {
      return $this$add3 + 3;
   }
}

때문에 add3() 메소드가 인자를 여럿 받는 경우를 가정한다고 하면 아래처럼 모킹해주면 된다.
항상 수신 객체가 첫 번째 인자에 위치한다!

class PlaygroundTest : StringSpec({
    "convertToStringAfterAdd3 should return result of Int.add3() as string" {
        // given
        val myClass = spyk(
            objToCopy = MyClass(),
            recordPrivateCalls = true,
        )

        val expected = 42
        every { myClass["add3"](ofType(Int::class), ofType(Long::class), ofType(Double::class)) } returns expected

        // when
        val result = myClass.convertToStringAfterAdd3(3)

        // then
        result shouldBe "$expected"
    }
}) {
    class MyClass {
        fun convertToStringAfterAdd3(value: Int): String
            = "${value.add3(1L, 3.14)}"

        private fun Int.add3(salt1: Long, salt2: Double): Int
            = this + salt1.toInt() + salt2.toInt() + 3
    }
}

예시라곤 요딴 것 밖에 만들지 못하는 비루한 나의 상상력...