[Kotlin] mockk으로 private 메소드 테스트 하기 + Slot 사용법

Jae Eon·2021년 12월 5일
1

Kotlin 공부

목록 보기
4/6

테스트 코드를 작성하면서 실제 DB와 연동하지 않고 Repository를 테스트 하기위해 Mockk을 사용해 검증했다.
또한 테스트 코드 작성중 불가피 하게 private 메소드를 검증 할 일이 생겼는데 그 방법을 정리 했다.
대부분 private 메소드를 테스트 하고 싶다는 건 책임 분리가 제대로 이루어 지지 않았다는 뜻이므로 최후의 수단으로 사용하자.

🍊 mockk 이란?

MockK는 코틀린을 위한 Mock 프레임워크이다.
자바에서의 Mockkito와 비슷한 역할을 한다.

Repository, Api호출 메소드와 같이 외부 의존성을 가지는 객체를 mocking 하여 결합도를 낮출 수 있습니다.

🍑 모의객체생성(mocking) 하기

  • mockk 생성하기
// 1. mockk<타입지정>()
private val entityRepository1 = mockk<EntityRepository1>()

// 2. 타입 추론 이용
private val entityRepository2 : EntityRepository2 = mockk()
  • spyk 생성하기

아래와 같은 엔티티 하나가 주입된 서비스가 있다고 가정한다.
엔티티는 count를 가지고 있고, update시 count가 10 증가한다.

@Service
class EntityService(
    private val entityRepository: EntityRepository
) {

    @Transactional
    fun updateCount(id: Long) {

        validate(id)

        val entity = entityRepository.findById(id)
            .orElseThrow { NotFoundException() }

        entity.count = entity.count + 10

        entityRepository.save(entity)
    }

    private fun validate(id: Long) {
        if (id <= 0) {
            throw IllegalArgumentException()
        }
    }
}

이제 EntityService를 테스트 하기위해 service를 spyk 객체로 생성을 해야한다.
updateCount가 실행 될 때, 내부 메소드들(repository의 findByid, save 뿐만 아니라 사용자가 만든 메소드)이
정상적으로 실행 됐는지 검증 할 수있다.

val entityRepository: EntityRepository = mockk()
val entityService: EntityService = spyk(EntityService(mappingRepository))

위 코드와 같이 mocking된 Repository를 주입해 생성하면 된다.

🍎 Private 메소드 테스트

선언할때 Private 메소드를 사용하겠다는 옵션을 넣어주면 된다.

val entityRepository: EntityRepository = mockk()
val entityService: EntityService = spyk(
	EntityService(mappingRepository),
    	recordPrivateCalls = true
)

메소드 사용은 아래와 같다.
validate() 호출시 항상 통과하게 만드는 코드다.

val id = 2
every { entityService["validate"](id) } returns Unit

🍋 Capturing(Slot) 사용법

사용된 인자를 Capturing하여 검증하고 싶을땐 Slot과 mutableList를 사용 할 수 있다.

val mock = mockk<Some>()

val slotId = slot<Long>()
every { mock.someMethod(capture(slotId)) } returns 0

mock.someMethod(5L)

val realId = slotId.captured
assertEquals(5L, realId)

You can capture an argument to a CapturingSlot or MutableList

val car = mockk<Car>()

val slot = slot<Double>()
val list = mutableListOf<Double>()

every {
  car.recordTelemetry(
    //makes mock match calls with any value for `speed` and record it in a slot
    speed = capture(slot), 
    //makes mock and capturing only match calls with specific `direction`.
    //Use`any()` to match calls with any `direction`
    direction = Direction.NORTH 
  )
} answers {
  println(slot.captured)

  Outcome.RECORDED
}


every {
  car.recordTelemetry(
    speed = capture(list),
    direction = Direction.SOUTH
  )
} answers {
  println(list)

  Outcome.RECORDED
}

car.recordTelemetry(speed = 15, direction = Direction.NORTH) // prints 15
car.recordTelemetry(speed = 16, direction = Direction.SOUTH) // prints 16

verify(exactly = 2) { car.recordTelemetry(speed = or(15, 16), direction = any()) }

confirmVerified(car)
profile
🖋정리를 안하면 잊어버린다.👣한 발자국씩 가보자!

0개의 댓글