티스토리 뷰

반응형

문제 상황


문제상황을 재현하기 위해 먼저 Person 클래스를 하나 생성하겠습니다.

class Person {
    fun isDeveloper(name: String, age: Int): Boolean {
        return true // 무조건 true를 반환
    }
}

 

만약 isDeveloper()의 파라미터를 Stub할때 동적인 값을 허용할려면 아래과 같이 사용할 것입니다.

val mock = mock(Person::class.java)
`when`(mock.isDeveloper(anyString(), anyInt())).thenReturn(true)

 

여기서 추가로 name은 "charlie"이지만 age값은 동적으로 받고 싶은 경우에 문제가 발생합니다.

// NullPointerException 발생
`when`(mock.isDeveloper(eq("charlie"), anyInt())).thenReturn(false)

 

name 부분을 anyString에서 eq("charlie") 로 변경하니 NullPointerException 에러가 발생했습니다.

 

자세한 에러내용은 아래와 같았습니다.

java.lang.NullPointerException: eq("charlie") must not be null

왜 갑자기 에러가 발생하는지 이해할 수 없었지만 한가지 확실한거는 eq("charlie") 부분때문에 에러가 발생하는거는 알 수 있었습니다.

그래서 eq("charlie") 부분을 집중적으로 살펴보겠습니다.

 

 

문제 해결


먼저 NullPointerException 에러가 발생하는 원인을 찾기 위해 ArgumentMatchers의 eq() 메서드를 살펴보았습니다.

public static <T> T eq(T value) {
    reportMatcher(new Equals(value));
    if (value == null) return null;
    return (T) Primitives.defaultValue(value.getClass());
}

eq() 메서드를 보니 if (value == null) return null 을 통해 null값을 다루고 있는것을 볼 수 있습니다.

현재 name필드는 null을 허용하기 않기 때문에 NullPointerException 에러가 발생한걸 확인할 수 있었습니다.

 

먼저 문제를 해결하기 위해 name 필드를 null을 허용하도록 변경하겠습니다.

class Person {
    // String -> String? 변경
    fun isDeveloper(name: String?, age: Int): Boolean {
        return true
    }
}

name필드를 String? 타입으로 변경하였더니 NullPointerException 에러가 더 이상 발생하지 않고 기존의 테스트 코드가 정상적으로 동작하게 됩니다.

발생하는 에러는 해결하였는데 한가지 찝찝한 부분이 있습니다.

테스트 코드를 동작시키기 위해 기존에 사용하던 Person 객체의 코드를 수정하게 된 부분입니다.

name필드를 null을 허용하도록 변경하므로써 테스트코드는 동작하였지만 이로 인해 name필드를 사용하는 다른 모든곳이 null처리를 해줘야 하며 null을 허용하므로써 다른곳에서 또다른 NullPointerException 에러가 발생할 취약점이 생겼습니다.

 

 

mockito-kotlin

그래서 기존 코드를 변경하지 않고 다른 방법으로 해당 문제를 해결해보겠습니다.

다른 방법은 mockito-kotlin 라이브러리를 사용하는 것입니다.

mokito-kotlin은 mockito를 kotlin으로 사용하기 좋게 한번더 랩핑시킨 라이브러리입니다.

mockito와 대부분 문법이 비슷하여 코드를 이해하는데 큰 어려움이 없으실 것입니다.

 

mockito-kotlin 라이브러리를 사용하기 위해서는 gradle에 아래 설정을 추가해야 합니다.

testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")

 

 

 

Person 객체

class Person {
    fun isDeveloper(name: String, age: Int): Boolean {
        return true
    }
}

 

Argument Matchers 사용

val mock = mock<Person>()
whenever(mock.isDeveloper(eq("charlie"), any())).thenReturn(false)

 

이렇게 mockito-kotlin의 Argument Matchers를 사용하면 NullPointerException이 발생하지 않고 정상적으로 코드가 동작하게 됩니다.

기존의 'when'을 mockito-kotlin에서는 whenever로 사용하는것을 제외하고는 코드도 동일합니다.

 

그러면 mockito와 동일하게 eq("charlie")를 사용하는데 왜 에러가 발생하지 않는지 알아보기 위해 똑같이 eq() 메서드를 살펴보겠습니다.

fun <T> eq(value: T): T {
    return ArgumentMatchers.eq(value) ?: value
}

기존 mockito의 ArgumentMatchers.eq()를 한번 더 감싸서 null을 반환하는 경우 value값을 리턴해주면서 NullPointerException 에러가 발생하지 않게 처리해주는것을 확인할 수 있습니다.

 

kotlin을 사용시에는 kotlin문법을 지원해주는 mockito-kotlin을 사용하는게 언어적인 차이로 인해 발생하는 에러를 최대한 줄이면서 개발할 수 있을것 같습니다.

추가로 Kotest, Mockk와 같은 kotlin 테스트를 지원해주는 라이브러리가 있기 때문에 여러가지를 비교해보시고 사용해보시길 추천드립니다!

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함