Hopp til hovedinnhold

MockK is a mocking library for Kotlin, written in Kotlin. Because of this, it has extensive support for Kotlin language features such as extension functions and companion objects.

What is mocking?

Mocking is a technique that is primarily used in unit tests, or tests that validates the functionality of parts of the code in isolation. When running unit tests on classes, they are often dependent on other classes to function properly.

As an example, imagine a Car class, which is dependent on another very complex Engine class in order to work. How do we isolate the testing of the car (the unit under test) from the engine (a dependency of the unit)?

A solution to this problem is to create fake versions of the dependencies. Instead of providing an actual engine for the car, we can instead provide a mock object which mimics the behavior of the dependency without all the underlying details, thus isolating the unit under test from its dependencies.

Mock objects can be configured in many different ways to suit the needs of different unit tests, and allows the test writer full control of the behavior of dependencies for any given test.

For reference, a mock object of the unit in focus in a unit test is often referred to as System Under Test (SUT), and the mocked dependencies of the SUT are often called Dependent-On Components (DOC).

How to mock in MockK

mockk and every

The bread and butter of MockK are the two functions mockk and every. You declare mock objects by using mockk, and you mock away dependencies by using every. This use of every is also called stubbing behavior to generate canned responses.

Let us consider a simple class for example demonstration purposes:

// Example class
class Santa(val title: String, val name: String): SantaI {
  fun getTitleAndName(): String =
    "$title $name"

  fun laugh(n: Int): String =
    "${"ho ".repeat(n).trim().capitalize()}!"
}

// You can also make a mock object directly from an interface
interface SantaI(val title: String, val name: String) {
  fun getTitleAndName(): String
  fun laugh(n: Int): String
}

Below are two simple examples of how mockk can be used to declare mock objects of the class Santa, and how every can be used to stub behavior.

// Example 1
val santaMock = mockk<Santa>()
every { santaMock.title } returns "Saint"
every { santaMock.name } returns "Nicholas"
every { santaMock.getTitleAndName() } returns "Sinterklaas"
every { santaMock.laugh(any()) } returns "Ho ho ho!"
// Example 2
val santaMock: Santa = mockk {
  every { title } returns "Saint"
  every { name } returns "Nicholas"
  every { getTitleAndName() } returns "Sinterklaas"
  every { laugh(any())} returns "Ho ho ho!"
}

The keyword returns specifies what the return value (or the canned response) for the call to that DOC should be.

The function call any() passed as parameter to laugh is a way of allowing every call with any parameter value to return a canned response with exactly three Ho ho ho!'s.

Argument matching

In addition to any, you can implement more sophisticated argument matching with the use of more, less, eq and or:

val santaMock: Santa = mockk {
  every { laugh(more(10)) } returns "Many ho's"
  every { laugh(less(1)) } returns "No ho's"
  every { laugh(eq(1)) } returns "Exactly one ho"
  every { laugh(or(1, 2)) } returns "One or two ho's"
}

Expected answers

In addition to returns, there are other keywords that control the behavior, or the expected answer, of a DOC.

The keywords returnsMany and andThen can be used to generate a sequence of canned responses from similar function calls.

val santaMock: Santa = mockk {
  // These following two lines do the same thing.
  every { laugh(any()) } returnsMany listOf("First ho", "Second Ho")
  every { laugh(any()) } returns "First ho" andThen "Second ho"
}

If the return type of a call to a DOC is Unit, just Runs can be used:

val santaMock: Santa = mockk {
  every { functionReturningUnit) } just Runs
}

You can also throw exceptions by using the keyword throws:

val santaMock: Santa = mockk {
  every { laugh(more(9000)) } throws LaughTooHardException("Ridiculous, no one laughs for this long.")
}

You can create more sophisticated answers in the form of lambda function return values by using the answers keyword in tandem with answer scope functions like firstArg:

val santaMock: Santa = mockk {
  every { laugh(any()) } answers { "Ho".repeat(firstArg<Int>()) }
}

Verifying behavior

While assertions and verification can typically be handled by your testing framework of choice (like Spek which was featured in yesterday's post), MockK does feature quite a bit of functionality to help the test writer verify behavior.

Behavior verification refers to checking if the mocked DOC's were called correctly from the SUT. Usually you want to put verification at the end of a test. You start a verification block by using any of the keywords verify, verifyAll, verifySequence and verifyOrder, and unlike every, you can specify multiple calls inside a single verification block.

To check if certain calls happened at least once, use a simple verify:

verify {
  santaMock.name
  santaMock.getTitleAndName()
  santaMock.laugh(2)
}

verifyAll does a similar check to verify, except it also checks if the calls specified are the only calls made on that mock. The following block checks if laugh was the only DOC called from the SUT:

verifyAll {
  santaMock.laugh(any())
}

verifySequence checks if an excact sequence of calls happened, with no other calls in between:

verifySequence {
  santaMock.laugh(1)
  santaMock.laugh(2)
  santaMock.laugh(3)
}

verifyOrder is similar to verifySequence, but it only checks the order of the calls specified, while allowing for other calls to be possibly woven in between:

verifyOrder {
  santaMock.laugh(1)
  santaMock.laugh(2)
  santaMock.laugh(3)
}

verify also accepts arguments. You can check if a call to laugh happened in an interval of 2 to 4 times by supplying arguments to the parameters atLeast and atMost:

verify(atLeast = 2, atMost = 4) {
  santaMock.laugh(any())
}

The parameter exactly specifies an exact number of calls to check for, and is the same as setting atLeast and atMost to the same value:

verify(exact = 1) {
  santaMock.laugh(any())
}

To check if a call did not happen, you can either set exactly = 0, or you can use the special construct wasNot Called:

// These two blocks do the same thing
verify(exactly = 0) {
  santaMock.laugh(any())
}

verify {
  santaMock.laugh(any()) wasNot Called
}

Sometimes you want to execute calls to the real dependencies of an SUT. For this purpose, MockK also features Spies. A Spy can be seen as a hybrid between a mocked object and a "real" object. Any verification you can apply to mocked objects, you can also apply to spies. You can create a spy with the function spyk, and add some optional mocked behavior like so:

val spySanta = spyk(Santa("Saint", "Nicholas"))
every { spySanta.name } returns "Nick"

That covers the basics of using MockK! If you're still interested in learning more, check out the relevant links below for further reading.

Did you like the post?

Feel free to share it with friends and colleagues