Kotest를 사용할때 주의할 점

필드를 모킹해도 필드에 대한 참조는 모킹되지 않을 수 있다

  1. 메서드 내부에서 구체 타입의 필드를 참조하는 경우
    • 필드에 대한 참조는 인스턴스를 모킹하는 시점에 캡쳐된다.
    • 따라서 필드를 모킹해도 메서드 내부의 참조는 모킹되지 않는다.

  2. 메서드 내부에서 추상 타입의 필드를 참조하는 경우
    • 필드에 대한 참조는 메서드가 실행되는 시점에 캡쳐된다.
    • 따라서 필드를 모킹하면 메서드 내부의 참조도 모킹된다.

예시

  • 상황
interface `자판기` {
    fun `음료수 뽑기`(): String
}

class `콜라 자판기`: `자판기` {
    override fun `음료수 뽑기`(): String {
        return "콜라"
    }
}

class `주스 자판기`: `자판기` {
    override fun `음료수 뽑기`(): String {
        return "주스"
    }
}


  • 결과
    1. 메서드 내부에서 추상 타입의 필드를 참조하는 경우
abstract class `자판기 이용자` {
   // 추상 타입의 필드
   abstract val `자판기`: `자판기`

   // 메서드 내부에서 추상 타입의 필드를 참조하고 있다
   fun `자판기에서 음료수 뽑기`(): String {
       return `자판기`.`음료수 뽑기`()
   }
}

val `자판기 이용자의 mock` = spyk<`자판기 이용자`>()

context("자판기를 콜라 자판기로 바꾸면") {
   // 추상 타입의 필드를 모킹한다
   every { `자판기 이용자의 mock`.`자판기` } returns `콜라 자판기`()

   test("자판기에서 음료수를 뽑으면 콜라가 나온다") {
       `자판기 이용자의 mock`.`자판기`.`음료수 뽑기`() shouldBe "콜라"
       // 성공
   }

   test("자판기에서 음료수를 뽑으면 콜라가 나온다") {
       `자판기 이용자의 mock`.`자판기에서 음료수 뽑기`() shouldBe "콜라"
       // 성공
   }
}


  1. 메서드 내부에서 구체 타입의 필드를 참조하는 경우
class `자판기 이용자` {
   // 구체 타입의 필드
   val `자판기`: `자판기` = `주스 자판기`()

   // 메서드 내부에서 구체 타입의 필드를 참조하고 있다
   fun `자판기에서 음료수 뽑기`(): String {
       return `자판기`.`음료수 뽑기`()
   }
}

val `자판기 이용자의 mock` = spyk<`자판기 이용자`>()

context("자판기를 콜라 자판기로 바꾸면") {
   // 구체 타입의 필드를 모킹한다
   every { `자판기 이용자의 mock`.`자판기` } returns `콜라 자판기`()

   test("자판기에서 음료수를 뽑으면 콜라가 나온다") {
       `자판기 이용자의 mock`.`자판기`.`음료수 뽑기`() shouldBe "콜라"
       // 성공
   }

   test("자판기에서 음료수를 뽑으면 콜라가 나온다") {
       `자판기 이용자의 mock`.`자판기에서 음료수 뽑기`() shouldBe "콜라"
       // 실패
       // 메서드는 "콜라"가 아닌 "주스"를 반환한다
   }
}