Mocking is not rocket science: MockK advanced features
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
, verify
— coVerify
, answers
— coAnswers
. Such functions are taking suspend lambdas instead of regular lambdas and allow to call suspend
functions.
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 position
and 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 pkgfun 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 Runsverify { 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 mockkClass
function.
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.