
MockK - library for mocking in Kotlin
Kotlin is still a very new technology and this means that there are many opportunities to do something better. For me, this was the way to go. I started writing a simple web processing layer on Netty and coroutine. Everything was in order, I even did something like a web framework with routing, web sockets, DSL and complete asynchrony. For the first time, everything seemed easy to learn. Indeed, coroutines make callbacks a linear and readable code.
Surprise was waiting for me when I started testing it all. It turns out Kotlin and mocking are difficult to compatible things. First of all, because of the final fields. Further, there is exactly one library for testing kotlin and this is Mockito. A wrapper has been created for her that provides something like DSL. But here, not everything is smooth. First of all, it is testing functions with named parameters. Mockito requires setting absolutely all parameters in the form of matchers, and in Kotlin there are often a lot of these parameters and some of them have default values. To ask them all is too expensive. In addition, the lambda block is often passed as the last parameter. Create an ArgumentCaptor and perform complex casting in order to call it - busting. Coroutines themselves are functions with the last parameter of type Continuation. And it requires special handling. Mockito added it, but they did not add the convenience of calling the most coroutines. In total, from all these trifles there is a feeling that this wrapper does not fit harmoniously into the language.
Having estimated the amount of work, I came to the conclusion that one person could well cope and began to write his library. I tried to make it close to the language and solve the problems that I encountered when testing.
Now I will tell you what happened. Here is the simplest example for seed:
val car = mockk()
every { car.drive(Direction.NORTH) } returns Outcome.OK
car.drive(Direction.NORTH) // returns OK
verify { car.drive(Direction.NORTH) }
No matchers are used here, just the overall DSL syntax is shown. First, the every / returns block specifies that the mock should return, and the verify block to verify whether the call was made.
Of course, in MockK there is the ability to capture variables, many matcher-s, spy-and other constructions. Here is a more detailed example . All this is also in Mockito. Therefore, I would like to describe the differences.
So, for all this to work, it took me a Java Agent to remove all final attributes. It is not at all difficult, it works from Maven / Gradle, but it does not work very well with the IDE. Each time you need to assign the “-javaagent: <some path>” parameter. There was even an idea to write plugins for popular IDEs that make it easy to run Java agents. But as a result, I had to make support for running JUnit4 and JUnit5 without the Java Agent.
For JUnit4, this is a launch using the standard @RunWith annotation, which I myself do not like, but have nowhere to go. To make life easier somehow, I added ChainedRunWith. It allows you to specify the next Runner in the chain and thus use two different libraries.
For JUnit5, just add a dependency on the JAR with the agent, and all the magic will happen by itself. But I can say that in implementation this is a real hack with Unsafe, Javassist and Reflection. For this reason, launching through the Java Agent is still considered the official way to start.
The next feature is the ability to set not all parameters as matchers, but only a part of them. To realize this opportunity, I had to scratch my brain. If we have such a function:
fun response(html: String = "",
contentType: String = "text/html",
charset: Charset = Charset.forName("UTF-8"),
status: HttpResponseStatus = HttpResponseStatus.OK)
And somewhere there is his call:
response(“Great”)
To test this in Mockito, you must specify all the parameters:
`when`(mock.response(eq(“Great”), eq("text/html"),
eq(Charset.forName(“UTF-8”)), eq(HttpResponseStatus.OK)))).doNothing()
This is clearly limiting. In MockK, you can specify only the necessary matchers, all other parameters will be replaced by eq (...) or, if matcher allAny () is specified, then by any ().
every { response(“Great”) } answers { nothing }
every { response(eq(“Great”)) } answers { nothing }
every { response(eq(“Great”), allAny()) } answers { nothing }
This is achieved by such a trick: the every block is called several times and each time the matcher returns a random value, then the data is matched and the necessary matchers are found. For places where no matcher is specified, the argument will almost always be constant. “Almost always” because sometimes the default parameter will be a function that returns time or something like that. This is easily circumvented by explicitly specifying a matcher.
Further about testing DSL. For example, consider this code:
fun jsonResponse(block: JsonScope.() -> Unit) {
val str = StringBuilder()
JsonScope(str).block()
response(str.toString(), "application/json")
}
jsonResponse {
seq {
proxyOps.allConnections().forEach {
hash {
"listenPort"..it.listenPort
"connectHost"..it.connectHost
"connectPort"..it.connectPort
}
}
}
}
It doesn’t matter what he is doing now - it’s important that this is a composition of constructions from DSL collecting JSON.
How to test it? MockK has a special matcher captureLambda for this. Convenience lies in the fact that with one expression we can capture lambd and call it in response:
val strBuilder = StringBuilder()
val jsonScope = JsonScope(strBuilder)
every {
scope.jsonResponse(captureLambda(Function1::class))
} answers {
lambda(jsonScope)
}
To verify the correctness of the main code, you can compare the contents of the StringBuilder with a sample of what should be in the response. The only convenience is that the block passed as the last parameter is an idiom of the language, and it’s convenient to have a special way of processing it in the mocking framework.
Coroutine support is also not so much a difficult-to-implement function, but just a convenient way to do what the language represents out of the box. We simply replace the call every and verify with coEvery and coVerify and we can call the coroutine inside.
suspend fun jsonResponse(block: JsonScope.() -> Unit) {
val str = StringBuilder()
JsonScope(str).block()
response(str.toString(), "application/json")
}
coVerify { scope.jsonResponse(any()) }
As a result, the goal of the project is to make mocking in Kotlin as convenient as possible, and not to build the thousands of functions that PowerMock and Mockito have. I will strive for this further.

I ask the public not to judge strictly, try the library in their projects and offer new functions, bring to mind the current ones.
Project website: http://mockk.io
Only registered users can participate in the survey. Please come in.
What will I do after reading the article?
- 81.8% try to use 27
- 21.2% I will use Mockito 7
- 15.1% if I find errors - send a report about them 5
- 3% will help with new features 1
- 3% will help with the documentation 1
- 6% testing with mock I don't need 2