쟈미로그

Mockito 동작 원리 (+ Mockito 사용 시 static 메소드를 목킹 못하는 이유) 본문

Test

Mockito 동작 원리 (+ Mockito 사용 시 static 메소드를 목킹 못하는 이유)

쟈미 2023. 9. 6. 00:06

서론

Mockito는 static 메소드를 목킹할 수 없다 (+final, private ..)

Kotlin+Spring 환경에서 개발하면서 알게된 것 중 하나가 'Mockito로는 static 메소드를 목킹할 수 없다'는 것이다.

이 사실을 깨달은 경위는, 코틀린에서 확장 함수를 목킹하고 싶은 상황이 와서 목킹하려하자 에러가 뜨는 것이었다. (코틀린 확장함수는 자바로 디컴파일 시 static final 혹은 final 메소드로 변환된다.)

 

이 목킹하려는 확장 함수가 내가 짠 코드면 모르겠는데.. Spring Data JPA가 기존 Optional을 반환하는 findById 대신, 코틀린을 위해 제공하는 확장 함수인 findByIdOrNull을 목킹하려다 일어난 일이었다.

(아래와 같이 findByIdOrNull을 목킹하려했을 때 다음과 같은 에러가 난다.)

  @Test
  fun mockitoTest() {
  	...
    // when
    `when`(userRepository.findByIdOrNull(1L)) // findByIdOrNull stub
      .thenReturn(User("B", null, mutableListOf(), 1L))
    ...
}

일단 이 문제는 MockK로 목 라이브러리를 옮기면서 해결했는데.. 왜 MockK에선 문제 없는데 Mockito에선 문제가 있는지 알아보기 위해서 Mockito의 동작 원리를 공부해보겠다!

 

 

Mockito의 동작 원리

목 라이브러리들이 목 객체를 어떻게 만들까 생각해보자. 기존 클래스가 있고, 이에대한 가짜(?) 객체를 만들어서 특정 응답을 고정시키고.. 뭔가 되게 특정 클래스에 대한 프록시 객체를 만들 것만 같은데? 

 

역시나 많은 목 라이브러리들이 채택하고 있는 방식이 프록시 기반이라고 한다! Mockito도 이 방식을 채택한 라이브러리다.

그런데 이외에도 바이트 코드 조작을 기반으로 하는 라이브러리들도 있었다. MockK가 이 방식을 채택한 라이브러리였다.

  1. 프록시 기반 목 라이브러리 : Mockito, EasyMock..
  2. 바이트코드 조작 기반 목 라이브러리 : MockK...

 

오? 이것만 봐도 사실 서론에서 말한 문제의 원인이 밝혀졌다.

동적으로 프록시 객체를 만드는 방식은 (CGLib 기준) 상속이다. 그리고 상속은 private, final, static 메소드는 불가능하다!! 그래서 Mockito에서 만든 목 객체에서, private/final/static 메소드를 stub 하려하면? 당연히 에러가 발생하는 것..

 

 

결론

이렇게 프록시 기반의 목 라이브러리는 간단한 반면 이런저런 제약들이 존재하는 것을 동작 원리와 함께 공부해보았다.

 

코틀린 공부를 하면서, 코틀린은 좀 더 안전한 동작을 위해서 정적 바인딩을 선호한다고 배웠다. (Java To Kotlin 1장 참고)

그래서인지 확장함수도 정적 바인딩되고, 변수 선언 시에도 val(자바의 final)을 권장하는 것 같다. 코틀린이 이렇게 정적 바인딩을 선호하므로, (자바로 따지면 final or static인 경우가 많아서) Mockito처럼 프록시 기반의 라이브러리를 사용하면 제약 사항이 많아질 것 같다.

 

즉, 코틀린으로 개발할 때는 바이트코드 조작 기반의 목 라이브러리를 사용하는 것이 제약에 구애받지 않고 개발할 수 있을 것 같음!!

 

 

 

 

 

'Test' 카테고리의 다른 글

Kotest와 MockK을 찍먹해보자!~  (0) 2023.08.15
Comments