MockK library for mocking-and in Kotlin
Kotlin is still very new technology and it means that there are many opportunities to do something better. For me this path was. I started to write a simple layer of web treatment on Netty and coroutine-Ah. Everything was fine, I even did something like a web framework with routing, web sockets, DSL and full asynchrony. For the first time, everything seemed easy to learn. Indeed, coroutine s doing from the noodle Korbakov linear and readable code.
a Surprise awaited me when I began to test it all. It turns out that Kotlin and mocking difficult compatible things. Primarily due to final fields. Next, there is exactly one library for testing Kotlin and it's Mockito. For a wrapper, which provides something like DSL. But it is not all smooth. First and foremost is the testing of functions with named parameters. Mockito requires you to set all the parameters in the matcher s, Kotlin in these settings, often a lot and some of them have default values. Ask them all too expensive. In addition, often the last parameter is passed to the lambda block. To create an ArgumentCaptor and perform the casting in order to call him — too much. Themselves coroutine is a function with the last parameter of type Continuation. And it requires special handling. In Mockito it was added, but not added convenience, call Korotin. Total of all these little things one gets the feeling that the wrapper was not very fit harmoniously into the language.
having Estimated the amount of work I have come to the conclusion that one person could handle and began to write his library. I tried to make it close to the language and solve the problems I encountered when testing.
Now I'll tell you what I came up with. Here for starters is a simple example:
the
val car = mockk<Car>()
every { car.drive(Direction.NORTH) } returns Outcome.OK
car.drive(Direction.NORTH) // returns OK
verify { car.drive(Direction.NORTH) }
it does not use the matcher, just in General, shows the syntax of the DSL. First block every/returns specifies that the mock should return, and a verify unit for verifying whether the invocation is made.
of Course, in MockK have the ability to capture variables, many matcher s, spy-and other structures. Here is a more detailed example. All this is also in Mockito. So I would like to describe the differences.
so for this to work, I took a Java Agent to remove all the final attributes. It is not difficult, works from Maven/Gradle, but does not work well with the IDE. Every time you need to add “-javaagent:<some path>” parameter. There was even the idea to write plugins for popular ides that allow you to easily run Java Agent-s. But in the end, I had to make a support for running JUnit4 and JUnit5 without Java Agent.
For JUnit4 is run through the standard annotation @RunWith, which he did not love, but nowhere to go. In order to somehow make life easier, I added ChainedRunWith. It allows you to ask the next Runner in the chain and thus to use two different libraries.
For JUnit5 enough to add a dependency on the JAR with the agent, and all the magic will happen by itself. But I can say that in the implementation it is a mere hack with Unsafe, Javassist and Reflection. For this reason, the official way to run all the same considered to run through the Java Agent.
the Following feature — ability to specify all parameters as the matcher, but only part of them. To implement this, I had to think about it. If we have here this function:
the
fun response(html: String = "",
contentType: String = "text/html",
charset: Charset = Charset.forName("UTF-8"),
status: HttpResponseStatus = HttpResponseStatus.OK)
And somewhere in there his call:
the
response(“Great”)
to test this in Mockito, it is necessary to specify all options:
the
`when`(mock.response(eq(“Great”), eq("text/html"),
This is clearly limiting. In MockK you can specify only the required matcher-s, all other parameters will be replaced by eq(...) or, if specified matcher allAny(), any().
the every { response(“Great”) } answers { nothing }
every { response(eq(“Great”)) } answers { nothing }
every { response(eq(“Great”), allAny()) } answers { nothing }
This is accomplished in this trick: every block is called several times and each time the matcher returns a random value, then the data are mapped and the right matcher. For those places where the matcher is not defined, the argument will almost always be constant. "Almost always" because sometimes the default is a function that returns a time or something like that. It is easy to get around explicitly specifying the matcher.
Next on the testing DSL. For example, consider this code:
the 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
}
}
}
}
No matter what he's doing now — it is important that the composition structures of the DSL and collecting JSON.
How to test? In MockK there is a special matcher captureLambda. The convenience lies in the fact that we are one expression can capture lambd and answer call it:
the val strBuilder = StringBuilder()
val jsonScope = JsonScope(strBuilder)
every {
scope.jsonResponse(captureLambda(Function1::class))
} answers {
lambda(jsonScope)
}
to verify That the main code can compare the contents of the StringBuilder as a sample of what should be the answer. Convenience only in that the block passed as the last parameter, is the idiom of the language, and it is convenient to have a special method to handle it in mocking frameworks.
support the coroutine is also not easy to realize the function as simply a convenient way to do something that the language provides out of the box. Just model every call and verify at coEvery and coVerify and can cause coroutine-in.
the suspend fun jsonResponse(block: JsonScope.() -> Unit) {
val str = StringBuilder()
JsonScope(str).block()
response(str.toString(), "application/json")
}
coVerify { scope.jsonResponse(any()) }
In the end, the goal is to make mocking in Kotlin as convenient as possible, not to build thousands of features of the PowerMock and Mockito. To this I will continue.
I Ask the public not to judge harshly, to try library in their projects and to suggest new features, bring to mind the current.
Website: http://mockk.io
Комментарии
Отправить комментарий