MockK is definitely a better alternative to other mocking frameworks if you use Kotlin. Previous article showed some basics. Now time to talk about such features as captured arguments, relaxed mocks, spies and annotations.

Capturing

Argument capturing can make your life easier if you need to get a value of an argument in every or verify block. Let’s say that we have following class:

class Divider {

fun divide(a: Int, b: Int) = a / b

}

There are two ways to capture arguments: using CapturingSlot<Int> and using MutableList<Int> .

CapturingSlot allows to capture only one value, so it is simpler to use.

val slot = slot<Int>()

val mock = mockk<Divider>()

every { mock.divide(capture(slot), any()) } returns 22

This creates a slot and a mock and set expected behavior following way: in case mock.divide is called, then first argument is captured to the slot and 22 is returned.

Now code being tested:

mock.divide(5, 2) // 22 is a result

After executing it, slot.captured value is equal to first argument i.e. 5

Now you can do some checks, assert for example:

assertEquals(5, slot.captured)

Besides that, you can use a slot in an answer lambda:

every {

mock.divide(capture(slot), any())

} answers {

slot.captured * 11

}

So mock.divide(5, 2) returns 55 .

That is basically it. Working with MutableList is the same, just instead of a slot in capture function MutableList should be used.

val list = mutableList<Int>()

val mock = mockk<Divider>()

every { mock.divide(capture(list), any()) } returns 22

This allows capturing several values from several calls in a row.

Relaxed mocks

By default mocks are strict. Before passing mock to a code being tested you should set behavior with every block. In case you do not provide expected behavior, and call is performed, library throws an exception.

This is different from what Mockito is doing by default. Mockito allows you to skip specifying expected behavior and replies with some basic value alike null or 0 . You can achieve the same and even more in MockK by declaring relaxed mock.

val mock = mockk<Divider>(relaxed = true)

Then you can use it right away:

mock.divide(5, 2) // returns 0

Besides that, if the return value is of reference type, library would try to create child mock and build chain of calls.

mock.call1(1, 2, 3).call2(4, 5, 6)

In verify blocks you can check if that calls were performed:

verify { mock.divide(5, 2) }

and

verify { mock.call1(1, 2, 3).call2(4, 5, 6) }

Note however that due to natural language limits it is not possible to have chains of calls work for generic return types as this information got erased. If you need such use-case, set expected behavior in every block explicitly.

Spies

Spies give the possibility to set expected behavior and do behavior verification while still executing original methods of an object.

Let’s say we have the following class:

class Adder {

fun magnify(a: Int) = a



fun add(a: Int, b: Int) = a + magnify(b)

}

We want to test the behavior of add function while mocking behavior of magnify function.

Let’s first create a spy:

val spy = spyk(Adder())

Here we create object Adder() and build a spy on top of it. Building a spy actually means creating a special empty object of the same type and copying all the fields.

Now we can use it, as if it was regular Adder() object.

assertEquals(9, spy.add(4, 5))

This checks that original method is called.

Besides that, we can define spy behavior by putting it to every block:

every { spy.magnify(any()) } answers { firstArg<Int>() * 2 }

This provides an expected answer for magnify function.

After that, behavior of add has changed because it was dependent on magnify :

assertEquals(14, spy.add(4, 5))

So we achieved our goal using spy.

Additionally, we can verify calls, as if it was a mock:

verify { spy.add(4, 5) }

verify { spy.magnify(5) }

This is a very powerful testing technique to employ.

Annotations

The library supports annotations @MockK , @SpyK and @RelxedMockK , which can be used as a simpler way to create mocks, spies, and relaxed mocks correspondingly.

class Test {

@MockK

lateinit var doc1: Dependency1



@RelaxedMockK

lateinit var doc2: Dependency2



@SpyK

val doc3 = Dependency3()



@Before

fun setUp() = MockKAnnotations.init(this)



@Test

fun calculateAddsValues1() {

every { doc1.call().add(any()) } returns 5

every { doc2.value2 } returns "6"

every { doc3.sub(any()) } returns 7



val sut = SystemUnderTest(doc1)



assertEquals(11, sut.calculate())

}

}

The important part here is MockKAnnotations.init(this) call which is executed at @Before phase. When it is executed all annotated properties are substituted with corresponding objects: mocks, spies and relaxed mocks.

Next article describes chained calls, object mocks, extension functions, and DSLs.

Thank you for your attention!