Mockk

Mockk 는 “mocking library for Kotlin” 이다. 간단한 사용법을 정리해보자.

Install

우선, 시작하기 앞서 dependency 를 추가해보자.

for gradle:

1
testImplementation("io.mockk:mockk:{version}")

for maven:

1
2
3
4
5
6
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>{version}</version>
<scope>test</scope>
</dependency>

Simple Example

mockk 를 활용한 간단한 테스트 코드를 작성해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
fun `sum`() {
// given
val additionCalculator = mockk<AdditionCalculator>()
every { additionCalculator.calculate(1, 3) } returns 4

// when
val result = additionCalculator.calculate(1, 3)

// then
verify { additionCalculator.calculate(1, 3) }
assertEquals(4, result)
}

위 코드에서 사용된 mockk functions 를 정리하자.

  1. mockk: mock 객체를 생성
  2. every: 어떻게 동작을 해야하는지 정의
  3. verify: 호출 여부 검증

Annotations

mockk 에서 지원하는 Annotations 을 활용하면, 테스트 코드를 더 쉽게 작성할 수 있다.

@MockK

@MockK 를 사용해서 mock 객체를 쉽게 생성할 수 있다.
JUnit5 기반이라면, MockKExtension 을 사용해서 mock 객체를 초기화할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ExtendWith(MockKExtension::class)
class MockKAnnotationTest {

@MockK // HERE !!
lateinit var sumCalculator: SumCalculator

@Test
fun sum() {
// given
every { sumCalculator.calculate(1, 3) } returns 4

// when
val result = sumCalculator.calculate(1, 3)

// then
assertEquals(4, result)
}
}

test function 파라미터에 @MockK 를 바로 사용할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ExtendWith(MockKExtension::class)
class MockKAnnotationWithFunctionParameterTest {

@Test
fun sum(@MockK sumCalculator: SumCalculator) { // HERE !!
// given
every { sumCalculator.calculate(1, 3) } returns 4

// when
val result = sumCalculator.calculate(1, 3)

// then
assertEquals(4, result)
}
}

@InjectMockKs

다른 클래스에 의존하는 클래스에 mock object 를 주입해서 mocking 하고 싶으면,

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
32
33
34
35
36
37
38
39
class Calculator(
private val additionCalculator: AdditionCalculator,
private val subtractionCalculator: SubtractionCalculator
) {
// ...
}

@ExtendWith(MockKExtension::class)
class InjectMocksTest {

@MockK
lateinit var additionCalculator: AdditionCalculator

@MockK
lateinit var subtractionCalculator: SubtractionCalculator

@InjectMockKs // HERE !!
lateinit var calculator: Calculator

@Test
internal fun `inject mocks`() {
// given
val num1 = 1
val num2 = 2

every { additionCalculator.calculate(num1, num2) } returns 3
every { subtractionCalculator.calculate(num1, num2) } returns -1

// when
val actualResult = calculator.calculate(num1, num2)

// then
val expectedResult = CalculationResult(
addition = 3,
subtraction = -1
)
assertEquals(expectedResult, actualResult)
}
}

Relaxed Mock

mock 객체가 어떤 동작을 해야하는지 정의하지말아보자.

1
2
3
4
5
6
7
8
9
10
11
@Test
fun `not relaxed`() {
// given
val additionCalculator = mockk<AdditionCalculator>()

// when
val result = additionCalculator.calculate(1, 2)

// then
assertEquals(3, result)
}

“no answer found for: AdditionCalculator(#1).calculate(1, 2)” 메세지로 에러가 발생한다.

stubbing 을 할 때, 기대하는 특정 행동에 대한 정의를 하고 싶지 않으면 relaxed mocking 을 하자.

1
2
3
4
5
6
7
8
9
10
11
@Test
fun `relaxed`() {
// given
val additionCalculator = mockk<AdditionCalculator>(relaxed = true) // HERE !!

// when
val result = additionCalculator.calculate(1, 2)

// then
assertEquals(0, result)
}


Comments