Mocking is not rocket science: MockK advanced features

Oleksiy Pylypenko
Kt. Academy
Published in
13 min readFeb 18, 2019

--

MockK is skyrocketing in Kotlin world during the last year. Users actively help to improve it by submitting issues and suggesting improvements. Last months, MockK introduced many powerful features and I am pleased to share them with you. This article is not short, because I want to share a lot about new possibilities you can use to improve your tests.

Hierarchical mocking

Since a very recent version, 1.9.1 MockK supports so-called hierarchical mocking. For example, if you have the following dependent interfaces:

interface AddressBook {
val contacts: List<Contact>
}
interface Contact {
val name: String
val telephone: String
val address: Address
}
interface Address {
val city: String
val zip: String
}

And need to mock the whole address book, you can use initialization block of mockk function to specify behavior inside of it and put other mocks by a chain in returns of every. This result in a nice DSL to define dependent objects:

val addressBook = mockk<AddressBook> {
every { contacts } returns listOf(
mockk {
every { name } returns "John"
every { telephone } returns "123-456-789"
every { address.city } returns "New-York"
every { address.zip } returns "123-45"
},
mockk {
every { name } returns "Alex"
every { telephone } returns "789-456-123"
every { address } returns mockk {
every { city } returns "Wroclaw"
every { zip } returns "543-21"
}
}
)
}

You can notice here that address.city and address.zip are mocked through chain calls. This is an alternative way and up to you which one to choose.

Real hierarchies, of course, will be consisting of a mix of real objects and mocks.

val serviceLocator = mockk<ServiceLocator> {
every { transactionRepository } returns mockk {
coEvery {
getTransactions()
} returns Result.build {
listOf(
NoteTransaction(
creationDate = "28/10/2018",
contents = "Content of note1.",
transactionType = TransactionType.DELETE
),
NoteTransaction(
creationDate = "28/10/2018",
contents = "Content of note2.",
transactionType = TransactionType.DELETE
),
NoteTransaction(
creationDate = "28/10/2018",
contents = "Content of note3.",
transactionType = TransactionType.DELETE
)
)
}

coEvery {
deleteTransactions()
} returns Result.build { Unit }
}

every { remoteRepository } returns mockk {
coEvery { getNotes() } returns Result.build {
listOf(
Note(
creationDate = "28/10/2018",
contents = "Content of note1.",
upVotes = 0,
imageUrl = "",
creator = User(
"8675309",
"Ajahn Chah",
""
)
), Note(
creationDate = "28/10/2018",
contents = "Content of note2.",
upVotes = 0,
imageUrl = "",
creator = User(
"8675309",
"Ajahn Chah",
""
)
), Note(
creationDate = "28/10/2018",
contents = "Content of note3.",
upVotes = 0,
imageUrl = "",
creator = User(
"8675309",
"Ajahn Chah",
""
)
)
)
}

coEvery {
synchronizeTransactions(any())
} returns Result.build {
Unit
}
}
}

(adopted, source: RegisteredNoteSourceTest.kt)

Coroutines

MockK supports coroutines from first days, but during last year, the internal engine became more advanced, and few syntactic clauses were added as well.

First, what you need to remember is that all functions to work with coroutines in MockK are built of regular function + prefix co . For example every becomes coEvery, verifycoVerify, answerscoAnswers . Such functions are taking suspend lambdas instead of regular lambdas and allow to call suspendfunctions.

coEvery { 
mock.divide(capture(slot), any())
} coAnswers {
slot.captured * 11
}

So from first sight, everything is very similar to regular mocking. But internally it is not as easy as it gets.

The first thing is that when the call to divide function gets intercepted it has implicit continuation parameter as the last argument on a byte-code level. Continuation is a callback to use when the result is ready. This means that the library needs to pass this continuation to the coAnswers clause, so it is not simply runBlocking the suspend lambda passed.

The second difference is that divide suspension function may decide to suspend. That means that internally it will return special suspension marker. When computations resume,divide is called again. As a result, each such resumed call will count as an additional invocation of the divide function. This means you need to take care of it during verification.

Understanding this internals is not a very pleasant job, but unfortunately, there is no other way to deal with it.

Verification timeout

When dealing with coroutines, people often forget that launch runs a job that executes in parallel and to verify results of such process you need one process to wait for another. This may be achieved with join. But then you need to explicitly pass the reference to Job everywhere.

To simplify writing such tests MockK supports verification with timeouts:

mockk<MockCls> {
coEvery { sum(1, 2) } returns 4
launch {
delay(2000)
sum(1, 2)
}
verify(timeout = 3000) { sum(1, 2) }
}

Simply saying that the verify clause will exit, either if verification criteria are met or will throw an exception in case of timeout. That way you have no need explicitly to wait till the moment computation in launch is finished.

Verification confirmation & recording exclusions

verifyAll and verifySequence are two verification modes where an exclusive set of calls are verified. There is no possibility that something goes wrong and called more times than needed.

On one handverifyOrder and simple verify do not work like that. On the other hand, they give you much more flexibility.

To address this difference, you can use a special verification confirmation call after all your verification blocks.

confirmVerified(object1, object2)

This will make sure that all calls for object1 and object2 were covered by verify clauses.

In case you have some less significant calls, you have the ability to exclude them from confirmation verification by excludeRecords construct.

excludeRecords { object1.someCall(andArguments) }

You can exclude a series of calls using argument matchers.

Varargs

Simple dealing with variable arguments was present in early MockK version, but from version 1.9.1 additional matchers were implemented.

Let me demonstrate this on a simple function that takes variable arguments:

interface VarargExample {
fun example(vararg sequence: Int): Int
}

Let’s mock it and see what are MockK possibilities:

val mock = mockk<VarargExample>()

First, it is possible to use it as simple arguments:

every { mock.example(1, 2, 3, more(4), 5) } returns 6

This means that if the variable argument array is of size 5 and it matches provided arguments, then the result of example function is 6.

It is of course not very flexible in case you need to deal with the variable length of variable arguments.

To overcome this limitation, MockK was enhanced with three matchers: anyVararg, varargAll and varargAny

every { mock.example(*anyVararg()) } return 1

This will simply match any possible variable argument array.

You can add any prefix or postfix of matchers.

every { mock.example(1, 2, *anyVararg(), 3) } return 4

Which will match at least 3 elements array that starts from 1, 2 and ends with 3.

To make more advanced matching varargAll allow providing a lambda that will set a condition that all arguments(except prefix and postfix) should be met.

every { mock.example(1, 2, *varargAll { it > 5 }, 9) } returns 10

This matches the following arguments arrays:

mock.example(1, 2, 5, 9)mock.example(1, 2, 5, 6, 9)mock.example(1, 2, 5, 6, 7, 9)

as well as:

mock.example(1, 2, 5, 5, 5, 9)

and so on.

Additionally in the scope of this lambda positionand nArgs properties are available to allow more sophisticated matching expressions.

every { mock.example(1, *varargAll { nArgs > 5}) } returns 6

Which will match only if passed 6 or more arguments to example where the first argument is 1

varargAny is a similar construct that requires at least one element to match the condition passed in lambda. All other things such as prefixes, postfixes, parameters position and nArgs are the same.

Extension & top-level functions

To successfully mock extension and top-level functions you need to understand how it works under the hood.

Top-level functions

First, let me show how to mock top-level functions.

Kotlin translates such functions to static methods of the special class created for your source code fragment. So in case you have lowercase in Code.kt source file on a byte-code level it will create CodeKt class with lowercase static method.

For instance, the following example:

// Code.kt source filepackage pkgfun lowercase(str: String): String {...}

translates to:

package pkg;class CodeKt {
public static String lowercase(String str) {...}
}

Now because you are not able to reference class CodeKt in your Kotlin code, you need to tell MockK this class name by using string parameter to mockkStatic. Afterward lowercase may be used in different mocking expressions.

mockkStatic("pkg.CodeKt")every { lowercase("A") } returns "lowercase-abc"

To know exactly where such function as lowercase will land you need to check actual class files produced by the build.

Sometimes names are fancy. For example:

mockkStatic("kotlin.io.FilesKt__UtilsKt")
every { File("abc").endsWith(any<String>()) } returns true

In Kotlin there is a special directive to guide compiler what file to put such top-level functions. It is called @file:JvmName

@file:JvmName("KHttp")package khttp
// ... KHttp code

In mockkStatic you just need to use this name:

mockkStatic("khttp.KHttp")

Extension functions attached to class or object

Mocking extension functions that are bound to a class or object as well needs some explanation.

You need to understand that dispatch receiver (this related to the class or object containing a declaration of extension function) on a byte-code level is passed as JVM this.

And the extension receiver (this that is related to the class extension of which we are performing) will be the first argument on a byte-code level. That way it is possible to put an argument matcher for an extension receiver. On the byte-code level, it will be anyway just first argument at the end.

class ExtensionExample {
fun String.concat(other: String): String
}
val mock = mockk<ExtensionExample>()with (mock) {
every { any<String>().concat(any<String>()) } returns "result"
}

Top-level extension functions

To mock the top-level extension function, you need to combine both approaches.

// Code.kt source file
package pkg
fun String.concat(other: String): String

Then in a test, you need to create static mock and call the function with argument matchers as parameters:

mockkStatic("pkg.CodeKt")
every { any<String>().concat(any<String>()) } returns "fake value"

As usual, you can use constants or the mix of constants and matchers in place of arguments:

mockkStatic("pkg.CodeKt")
every { "abc".concat("def") } returns "abc-and-def"

Hopefully, this explanation will make attempts to mock extension and top-level functions less painful. If not please let me know in comments what is unclear and I will try to adjust the article for better understanding.

Object & enumeration mocks

Mocking objects is very simple. You just pass an object to mockkObject and objects becomes a spy. This allows to still use it as if it was an original object, but you can stub, record and verify behavior.

object ExampleObject {
fun sum(a: Int, b: Int): Int
}
mockkObject(ExampleObject)
every { ExampleObject.sum(5, 7) } returns 10

This approach may be applied to any object. Either created from a class, objects declared by object clause, companion objects, or enum class elements.

Mock relaxed for Unit returning functions

Strictness is a nice thing, but sometimes there is no much sense to have a lot of statements that mock functions returningUnit without real behavior or result. In such a case, MockK provides a compromise between full strictness and full relaxation.

You can create a so-called “mock relaxed for unit returning functions”:

val mock = mockk<ExampleClass>(relaxUnitFun = true)

For such mock functions returning Unit you are not obliged to specify behavior. This way you can reduce boilerplate and still maintain strictness in all other cases.

Mocking functions returning Nothing

Returning Nothing is a special case in Kotlin language.

For example, you have a function returning Nothing:

fun quit(status: Int): Nothing {
exitProcess(status)
}

To mock it and not abuse type system and runtime the only choice you have is to throw an exception:

every { quit(1) } throws Exception("this is a test")

Constructor mocks

To convert just created (initialized with a constructor) objects to object mocks you can use so-called constructor mocks. Usually, you should avoid such design and either inject object or factory to tested object, but sometimes there is just no choice.

class MockCls {
fun add(a: Int, b: Int) = a + b
}
mockkConstructor(MockCls::class)every { anyConstructed<MockCls>().add(1, 2) } returns 4MockCls().add(1, 2) // returns 4

Here you see that to denote all objects constructed for certain class, anyConstructed is used. There is no better granularity available and you are not able to distinguish between constructors. Although in Kotlin usually only one primary constructor is created.

Private functions mocking

In rare cases, mocking private function may be required. This is tedious because you are not able directly to calls such a function. Generally, this approach is not recommended. Kotlin has a nice internal modifier that is visible from tests.

Anyway, situations may be different and saying that this should not be used at all is an oversimplification of the complex world.

The main problem is that such a private function should be referenced dynamically, and types of arguments should match exactly to choose it via reflection:

every { mock["sum"](any<Int>(), 5) } returns 25

Another thing is that such calls are not recorded by default, otherwise they would affect verifyAll, verifySequenece or confirmVerified constructs. To enabled recording pass recordPrivateCalls = true to mockk or spyk functions.

val mock = spyk(ExampleClass(), recordPrivateCalls = true)

Also, there is an alternative expression to [] operator:

every {
mock invoke "openDoor" withArguments listOf("left", "rear")
} returns "OK"
verify {
mock invoke "openDoor" withArguments listOf("left", "rear")
}

Properties mocking

Usually, you can mock properties as if it is get/set… functions or field access. But in case you have either private property or you need more verbose syntax, or you need access to field you can use alternatives:

every { mock getProperty "speed" } returns 33
every { mock setProperty "acceleration" value less(5) } just Runs
verify { mock getProperty "speed" }
verify { mock setProperty "acceleration" value less(5) }
every {
mock.property
} answers { fieldValue + 6 }
every {
mock.property = any()
} propertyType Int::class answers { fieldValue += value }
every {
mock getProperty "property"
} propertyType Int::class answers { fieldValue + 6 }
every {
mock setProperty "property" value any<Int>()
} propertyType Int::class answers { fieldValue += value }
every {
mock.property = any()
} propertyType Int::class answers {
fieldValue = value + 1
} andThen {
fieldValue = value - 1
}

Here you can see that propertyType is a special hint for a type of property in case compiler is not providing this information to MockK directly.

Class mock

When you need to create a mock of the arbitrary class specified by reflection KClass object, you can use mockkClassfunction.

val mock = mockkClass(ExampleClass::class)

Settings

MockK supports few global switches. To activate them you should place io/mockk/settings.properties in your classpath according to this template:

relaxed=true|false
relaxUnitFun=true|false
recordPrivateCalls=true|false

This allows to make mocks relaxed by default, either make them relaxed for a unit returning functions or record private calls.

Cleanup

There were many questions regarding correct cleanup so far. I realize that this is an important topic and would like to come up with a great solution for this. Although right now I have no understanding of all cases and it will be probably breaking changes.

Here I will just tell what current set of functions is doing. clear functions delete internal state of objects associated with mocks, unmock functions return back transformation of classes.

clearAllMocks — clear all mocks. If you have simple sequential tests this will be an optimal choice.

clearMocks — clear particularly specified regular or object mocks. Valuable in parallel tests as clearAllMocks may interfere with your other tests.

clearStaticMockk, clearConstructorMockk — similarly cleans particularly specified static or constructor mocks. I think if you are using the same classes for this mocks in parallel tests you can get to trouble. Test framework needs to isolate tests with different classloaders then. I am not sure if any framework is doing that.

unmockkObject — returns back transformation of particular regular or object mock.

unmockkAll — again probably if you have just sequential tests and need to return back everything to initial state this is the best choice.

unmockkStatic, unmockkConstructor — return back transformation of static mock. The similar case as withclearStaticMock — you probably not safe to have parallel tests with same classes.

mockkObject , mockkStatic , mockkConstructor has versions with last argument a lambda. It will do a corresponding unmockk operation afterward:

inline fun mockkStatic(vararg classes: String, block: () -> Unit) {
mockkStatic(*classes)
try {
block()
} finally {
unmockkStatic(*classes)
}
}

That is it for cleanup.

In this series of articles, we covered all the topics related to mocking and MockK. From the very basics to advanced features. This article was about advanced features. MockK has a pretty extensive set of them: hierarchical mocking, coroutines, verification timeout, verification confirmation & recording exclusions, varargs, extension & top-level functions, object & enumeration mocks, mock relaxed for Unit returning functions, mocking functions returning nothing, constructor mocks, private functions mocking, properties mocking, class mock, settings and cleanup.

Thank you!

Click the 👏 to say “thanks!” and help others find this article.

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow us on medium.

--

--