이번 포스팅에선 이전 포스팅에서 끝내지 못한 Mock 객체 선언 방법을 이어서 정리하려고 한다.
mockkClass
클래스를 기반으로 mock 객체를 만들 때 사용한다.
mockk는 제네릭을 사용하는 반면 mockkClass는 Class를 사용한다.
// mockkClass
private val userService = mockkClass(UserService::class)
// mockk
private val userService = mockk<UserService>()
사용방법은 mockk와 동일하다.
@Test
fun mockkTest() {
val userService = mockkClass(UserService::class)
every { userService.getUser() } returns null
assertNull(userService.getUser())
}
파라미터도 동일하다.
mockk로만으로도 충분한 거 같은데 mockkClass의 존재 이유를 잘 모르겠다.
알게되면 추가하겠다.
mockkObject
mockkObject는 enum, object 를 mock 객체로 만들 때 사용한다.
mockkObject를 스터빙하면 다른 테스트들에 영향이가니 꼭 unmock을 해줘야 한다. (자세한 건 여기서!)
mockkObject(Object)
mockkObject(Object)로 mock 객체로 만들 수 있다.
이전에 소개했던 것들과는 모양이 다르게 생겼다.
object 형태들을 대상으로 하는 메소드라 리턴 값이 Unit이다.
별도 스터빙을 하지 않으면 spy처럼 기존 로직을 실행한다.
enum
enum class Membership(
val label: String
) {
BRONZE("브론즈"),
SILVER("실버"),
GOLD("골드");
}
Membership enum class를 mock 객체로 선언해보겠다.
@Test
fun testEnum() {
mockkObject(Membership.BRONZE)
every { Membership.BRONZE.label } returns "골드"
assertEquals("골드", Membership.BRONZE.label)
}
잘 스터빙이 된 걸 볼 수 있다.
object
object Calculator {
fun plus(a: Int, b: Int): Int {
return privatePlus(a, b)
}
private fun privatePlus(a: Int, b: Int): Int {
return a + b
}
}
이전에 사용했던 Calculator를 object로 만들어 테스트해보겠다.
@Test
fun testObject() {
mockkObject(Calculator)
every { Calculator.plus(any(), any()) } returns 1000
assertEquals(1000, Calculator.plus(1, 2))
}
잘 스터빙이 된 걸 볼 수 있다.
혹시나 해서 mockk로도 object를 만들 수 있을까 테스트를 해봤는데
@Test
fun testObjectUsingMockk() {
val calculator = mockk<Calculator>()
every { calculator.plus(any(), any()) } returns 1000
assertEquals(1000, calculator.plus(1, 2))
}
mockk를 사용하면 변수로 선언을 해야하기 때문에 object class에 맞지 않는다.
근데 mock 객체로 선언되어 스터빙이 제대로 되긴 하는데 너무 어거지로 해야한다.
MockK를 써보면 어거지로 하면 되긴 하는데 그러지말고 용도에 맞는 걸 잘 찾아쓰는 게 중요한 거 같다.
파라미터
objects(vararg였군..)와 recordPrivateCalls 2개 있다.
이전에 알아본 파라미터라 지나가겠다.
mockkStatic
java static method, top level function, extension을 mock 객체로 선언할 때 사용한다.
mockkStatic을 스터빙하면 다른 테스트들에 영향이가니 꼭 unmock을 해줘야 한다. (자세한 건 여기서!)
mockkStatic(StaticObject)
별도 스터빙을 하지 않으면 spy처럼 기존 로직을 실행한다.
java static method
코틀린으로 구현된 메소드만 사용하면 좋겠지만 어쩔 수 없이 자바로 구현된 걸 사용해야 하는 경우가 많다.
java static method를 스터빙하려면 static 메소드를 가지고 있는 클래스를 mockkStatic으로 mock 객체로 만들어야 한다.
우리가 아주 자주 사용하는 LocalDateTime.now()는 java static method다.
이걸 스터빙해보자.
@Test
fun testJavaStaticMethod() {
mockkStatic(LocalDateTime::class)
every { LocalDateTime.now() } returns LocalDateTime.of(2023, 5, 3, 0, 0, 0)
assertEquals(LocalDateTime.of(2023, 5, 3, 0, 0, 0), LocalDateTime.now())
}
스터빙이 잘 된 걸 볼 수 있다.
top level function
fun helloWorld(): String {
return "hello world"
}
helloWorld top level function을 mock 객체로 만들어보자.
@Test
fun testTopLevelFun() {
mockkStatic(::helloWorld)
every { helloWorld() } returns "yeah"
assertEquals("yeah", helloWorld())
}
스터빙이 잘 된 걸 볼 수 있다.
extension
extension은 class-wide, object-wide, module-wide 총 3개가 있는데
class-wide, object-wide는 mockk로 가능하다.
data class Obj(val value: Int)
class Ext {
fun Obj.extensionFunc() = value + 5
}
@Test
fun testClassExtension() {
with(mockk<Ext>()) {
every { Obj(5).extensionFunc() } returns 11
assertEquals(11, Obj(5).extensionFunc())
}
}
근데 module-wide는 mockkStatic를 써야한다.
package com.example.mockk
fun helloWorld(): String {
return "hello world"
}
fun String.capitalizeWords(): String {
val words = this.split(" ")
val capitalizedWords = words.map { it.capitalize() }
return capitalizedWords.joinToString(" ")
}
helloWord top level function, String.capitalizeWords extension을 com.example.mockk.TopLevelFunction.Kt 파일에 선언해놨다.
String.capitalizeWords를 mock 객체로 선언해보자.
두 가지 방법이 있다.
첫 번째는 package 명 + 파일명 + 확장자까지 해서 통째로 mock 객체로 선언하는 방법이다.
@Test
fun testModuleExtension1() {
mockkStatic("com.example.mockk.TopLevelFunctionKt")
every { "test".capitalizeWords() } returns "test"
assertEquals("test", "test".capitalizeWords() )
every { helloWorld() } returns "yeah"
assertEquals("yeah", helloWorld())
}
두 번째는 아까 위에서 top level function에서 설명한 거처럼 쓸 수 있다.
@Test
fun testModuleExtension2() {
mockkStatic(String::capitalizeWords)
every { "test".capitalizeWords() } returns "test"
assertEquals("test", "test".capitalizeWords() )
every { helloWorld() } returns "yeah"
assertEquals("yeah", helloWorld())
}
mockkStatic("com.example.mockk.TopLevelFunctionKt"), mockkStatic(String::capitalizeWords) 두 방법 모두 파일 안에 있는 것들 전부를 mock 객체로 선언한다.
파라미터
별거없다.
mockkConstructor
생성자를 mock 객체로 선언할 때 사용한다.
mockkConstructor를 스터빙하면 다른 테스트들에 영향이가니 꼭 unmock을 해줘야 한다. (자세한 건 여기서!)
mockkConstructor(MockCls::class)
선언 방법은 이전 것들과 동일하다.
스터빙 하는 방법이 좀 다르다.
every { constructedWith<MockCls>().add(1) } returns 2
위 형태처럼 constructedWith<Class>(인자)로 사용해야 한다.
constructedWith를 사용하면 명시한 생성자별로 prototype mock이라는 걸로 생성이 된다.
prototype mock별로 호출이 record된다. 이게 무슨 소린지 아래서 알아보자.
MockCls의 생성자를 mock 객체로 만들어보자.
class MockCls(private val a: Int = 0) {
constructor() : this(2)
constructor(x: String) : this(x.toInt())
fun add(b: Int) = a + b
}
생성자가 총 3개인 클래스다. 전부 테스트를 해보자.
@Test
fun testConstructor() {
mockkConstructor(MockCls::class)
every { constructedWith<MockCls>().add(1) } returns 2
every { constructedWith<MockCls>(OfTypeMatcher<String>(String::class)).add(2) } returns 3
every { constructedWith<MockCls>(EqMatcher(4)).add(any()) } returns 4
assertEquals(2, MockCls().add(1))
assertEquals(3, MockCls("2").add(2))
assertEquals(4, MockCls(4).add(7))
verify {
constructedWith<MockCls>().add(1)
constructedWith<MockCls>(OfTypeMatcher<String>(String::class)).add(2)
constructedWith<MockCls>(EqMatcher(4)).add(7)
}
}
constructedWith<MockCls>(인자)로 스터빙을 하면 어디에 따로 명시하지 않아도 mock 객체가 생성되는데 이걸 prototype mock이라고 하는 것 같다.
그래서 아래 verify에 호출된 걸 각 prototype mock을 이용해 검증할 수 있다.
파라미터
총 3개의 파라미터가 있는데 localToThread는 처음본다.
자세한 사용방법을 잘 모르겠어서 찾아보고 추가하겠다.
'Kotlin' 카테고리의 다른 글
[Kotlin] 공식 코틀린 코딩 컨벤션 소개 (0) | 2023.06.06 |
---|---|
[Kotlin] MockK 사용법 (4) - Mock 객체 선언 해제(unmockkObject, unmockkStatic, unmockkConstructor, unmockkAll) (1) | 2023.05.17 |
[Kotlin] MockK 사용법 (2) - Mock 객체 선언 방법 (mockk<T>, spyk<T>, spyk(obj)) (0) | 2023.05.12 |
[Kotlin] MockK 사용법 (1) - MockK란? (0) | 2023.05.01 |
[Kotlin] LocalDateTime.parse() DateTimeParseException, 한글 깨짐 해결 방법 (0) | 2022.09.11 |
댓글