지금까지 Kotlin
+ JUnit5
+ Mockito
를 사용하여 단위테스트를 작성하였다. Mockito
와 Kotlin
을 같이 쓰기엔 불편한점이 있긴했지만 어쨋든 되긴하니 사용하고 있었다.
하지만 테스트 중에 repository.findByIdOrNull()
을 스텁하려하니 NPE 에러가 발생했다.
에러 로그
cannot be returned by findById()
findById() should return Optional
분명 findByIdOrNull
을 스텁했는데 findById()
는 Optional 객체를 반환해야한다는 에러 로그는 이해하기 힘들다.
구글링을 해보니 Mockito
는 정적 메소드에 대해서는 스텁할 수 없다고 한다. findByIdOrNull
는 코틀린에서 정의된 확장함수 이기 때문에 자바 코드로 디컴파일시 정적 메소드이기 때문에 에러가 발생한 것 같다.
이 문제를 해결하기 위해 findById 메소드를 사용하도록 코드를 바꾸는 것은 주객전도라 생각되어 모킹 프레임워크를 Mockito
에서 코틀린 프로젝트인 MockK
로 변경했다.
@ExtendWith(MockKExtension::class)
class TestClass {
...
}
MockK를 사용하기 위해서는 MockKExtension 확장 기능을 추가해야한다.
목 객체를 생성하는 것은 Mockito와 크게 다르지 않다.
// Mockito Mock
val mock1 = mock(MockClass::class.java)
// MockK Mock
val mock = mockk<MockClass>()
// stub
every { mock.method() } returns "OK"
every { mock.methodWithArgs(any()) } returns "OK"
// mockito stub
`when`(mock.method()).thenReturn("OK")
`when`(mock.methodWithArgs(anyString())).thenReturn("OK")
stubbing 은 every
메소드를 사용한다. 유의할 점은 Mockito
는 목 객체의 메소드를 stub 하지 않고 사용하면 null을 반환했지만 MockK
객체는 에러가 발생한다.
이것을 방지하려면 목 객체 생성시 relax 옵션으로 기본값을 설정해 RelaxedMock으로 만들어야 한다.
val mock = mockk<MockClass>(relax=true)
mock.method() // true 반환
목 객체의 함수 호출 여부 검사는 verify
메소드를 사용한다.
public fun verify(
ordering: Ordering,
inverse: Boolean,
atLeast: Int,
atMost: Int,
exactly: Int,
timeout: Long,
verifyBlock: MockKVerificationScope.() -> Unit
): Unit
// 정확히 한번 실행
verify(exactly = 1) { mock.method(1) }
// 1000ms 내 실행
verify(timeout = 1000L) { mock.method(1) }
Mockk
는 MockBean
, SpyBean
과 같은 기능을 제공하지 않는다. 따로 사용하고 싶다면 Ninja-Squad/springmockk
의존성을 추가하고 @MockkBean
, @SpykBean
어노테이션을 사용해야 한다.
참조
코틀린 mock 프레임워크 MockK 소개
Kotlin에서 mock 테스트 하기
https://techblog.woowahan.com/5825/