Kotlin/Kotlin | Spring 학습기록

[Kotlin] mock 테스트를 작성할 때 기억하면 좋은 점들

kth990303 2024. 10. 12. 16:13
반응형

스프링 환경에서 테스트 코드를 작성할 때, unit test가 아닌 이상 모킹을 이용해야 하는 경우가 참 많다.

나의 경우는 테스트를 하고 싶은 객체의 함수 외에, 관심사가 아닌 함수들과 의존객체들을 주로 mock 으로 처리하는 듯.

 

이번 포스팅에서는 어떻게 보면 굉장히 단순하지만, 최근 내가 잊고 있었던 내용들을 기록해보려 한다.

`굳이 기록까지 해야되나?` 싶기도 하다. 하지만 가끔씩 상기시키면 좋을 듯한, 그리고 요즘같이 바쁜 시기에 이걸 잊으면 맞왜틀? (맞게 작성했는데 왜 테스트 안돌아감?) 을 할 수 있을듯한 내용들이라.. 나를 위해 기록하려 한다.

 


data class 는 mock 이 불가능하다.

아무 생각없이 data class mock을 반환하는 테스트 코드를 작성한 적이 있을 것이다. 

하지만 data class는 모킹할 수 없다.

모킹하려 한다면 아래 에러를 만나게 된다.

 

Cannot mock/spy class ~~
Mockito cannot mock/spy because :
 - final class
org.mockito.exceptions.base.MockitoException: 

 

이는 Mockito.mock() 에서 mock 객체를 만들 때, 사용하는 클래스를 상속받아 proxy클래스를 생성하여 메서드를 오버라이딩하기 때문이다. kotlin 에서 final 클래스는 상속이 불가능하므로 data class는 mock이 불가능한 셈.

 

만약 data class를 모킹하고 싶다면 아래 라이브러리를 이용하면 된다. 

https://mvnrepository.com/artifact/org.mockito/mockito-inline

 

근데 굳이 data 클래스를 모킹하지 않고, 실 객체를 사용하여 스터빙할 때 반환하게 하는 것이 나은 것 같다.

data 클래스 내부 필드들만 모킹하고, data 클래스 자체는 실 객체를 이용하면 오히려 테스트가 편한 듯.


mockitokotlin2 에서의 any() 는 any(T::class) 가 지원되지 않는다.

코틀린으로 mock 테스트를 작성하기 위해 any() 와 같은 ArgumentMatchers 도구들을 사용하려 한 경험들이 있을 것이다.

다양한 any()

 

그런데 가끔 아무 생각없이 any를 import하면, any(Member::class) 와 같이 사용하면 빨간줄이 뜬 경험이 존재할 것이다.

 

위에서 보면 알 수 있다시피,

mockitokotlin2 에서는 any() 만 제공한다.

kluent 에서는 any(), any(kClass: KClass<T>) 를 제공해준다.

 

음, 그렇다면 kluent의 any() 와 mockitokotlin2 의 any() 차이점이 뭘까?

kluent 패키지를 한번 살펴보자.

kluent 에서 any(), mock() 등 다양한 기능들은 nhaarman.mockitokotlin2 의 기능들을 사용한다

 

빨간 박스에 있는 부분들을 주목해보자.

kluent 에서 any(), mock() 등 다양한 기능들은 nhaarman.mockitokotlin2 의 기능들을 사용한다.

따라서 차이점이 없는 셈이다.

(참고로 kluent의 Verify 와 mockitokotlin2 의 verify() 도 마찬가지다.)

 

조금 더 들여다보자.

이번에는 mockitokotlin2 패키지 쪽이다.

 

mockitokotlin2 에서는 Mockito.any()를 사용한다.

빨간 박스 외의 부분들도 살펴봐도 좋다. mockitokotilin2 에서는 Mockito의 기능들을 사용하는 것을 확인할 수 있다.

 

 

아래는 chatgpt 에게 물어본 mockitokotlin2 의 특성이다.

 

장점:
- Mockito의 기능 확장: 기존 Mockito의 기능을 그대로 활용할 수 있어, Java에서 사용하는 다양한 모킹 및 스텁 기능을 Kotlin에서도 쉽게 사용할 수 있습니다.

단점:
- Mockito에 의존: 기본적으로 Mockito에 의존하므로, Mockito가 제공하지 않는 기능은 사용할 수 없습니다.

 

 

결론)

kluent 에서는 any(), any(KClass) 타입을 지원해준다.

kluent 는 mockitokotlin2 의 매쳐를, 그리고 mockitokotlin2는 Mockito 클래스의 매쳐를 사용한다.


 

(any()) must not be null 에러가 뜨는 경우, any(KClass::class) 로 명시해주자.

스터빙할 때 굉장히 자주 뜨는 에러다.

실제로, 며칠 전에도 저 에러로 고생 좀 했다.

 

저 에러는 스터빙할 때 itReturns (또는 thenReturns) 결과가 null이 될 수 있다는 것으로 오해할 수 있다.

하지만 실제로는 함수의 argument로 null이 들어올 수 없다는 에러이다.

따라서 아규먼트매처의 타입을 명확히 지정해주면 해결된다.

 

any() 타입은 null 반환이 가능하나, any(KClass::class) 타입으로 명시해주는 경우에는 타입이 명확히 지정되므로 해당 에러가 뜨지 않는 것을 확인하였다.

 

itReturns (또는 thenReturns) 로는 any(), any(KClass::class), mock 객체 어떤 거를 넣어도 상관없었다.

 

 

참고로 argumentCaptor를 이용하여, 해당 아규먼트 자체를 테스트하기 위해 해당 아규먼트에 captor.capture()를 넣어도 위 문제는 발생하지 않는다.

argumentCaptor 클래스 내부 코드

 

@Suppress("UNCHECKED_CAST") 때문인데, 해당 코드 블록에서 발생하는 Unchecked Cast 경고를 무시하도록 컴파일러에 지시하기 때문인 것으로 보인다.


 

요즘 블로그에 글을 잘 못쓰고 있다.

최근 참여하고 있는 프로젝트가, 유관부서가 정말 많이 연관돼있고 도메인 로직이 굉장히 중요한 프로젝트이기 때문이다.

 

물론 이 프로젝트에서도 기술적인 학습은 많이 하고 있다.

하지만, 이 프로젝트에서 나의 목표는 도메인 로직 및 유관부서 영향도 파악이다. 따라서 기술적 학습보다도, 도메인 파악이 우선시되다 보니 비교적 기술학습 및 블로그작성이 후순위로 미뤄지고 있는 것이 한몫하는 듯하다. (근데 사실 핑계다. 좀 더 노~력 했다면.. 둘 다 챙길 수 있을지도 ㅋㅋ)

 

그리고 요즘은 클라이밍 취미활동도 챙기고, 학교수업도 참여하다보니 기술적 학습이 더욱 미뤄지고 있는듯하다.

아마 다음 포스팅은 우아콘 참여 후기이지 않을까?

 

반응형