MockK main intention is to be a convenient mocking library for everybody who develops in Kotlin. This means as much as possible support for language features and constructs.
Kotlin classes and functions are final by default, that is why MockK employs by default inlining class transformation technique to build proxies. This means that the code of existing methods is transformed the way it includes calls to a mocking library and mocking library decides what to do next with execution. This has a trade-off to performance, but definitely only possible solution for Kotlin language.
Next step towards Kotlin is support of DSL for specifying behavior and verification of mocks/spies. Check these examples, they are very compact and concise:
The DSL is built as a layer over so-called MockK Gateway API which defines a set of interfaces to interact with MockK implementation. This way theoretically MockK can have extensions and other programmatic control, without employing a layer of human-readable DSL for such purpose. At least if such cases appear it should be relatively easy to modify this layer to support them.
Kotlin has default arguments as first-class citizens and it was a great innovation to support them in MockK. The idea is simple. Everything in every or verify block is executed several times with matchers returning random values. Then all this information get analyzed and correspondence of matchers to arguments is revealed. Every argument that is passed same value from one call to another is considered a constant and everything returning specific random values is tied to matcher. Default values usually return same values, so no need to specify them at all. Which allows us to have a function with dozens of default parameter and provide matchers only for a few. All defaults will be captured automatically.
Next thing is a support for chained mocking. This makes DSL naturally describe a series of calls. It has a little bit more power than usually people expect from it because it is combined with the previous feature and allow to identify a result of a call that is put as an argument to other call.
There is one drawback here, which makes it work not so well with generic functions. For example, one call is returning
T : Any and another call is taking this return value as a parameter. Let the type of parameter be
Int. The compiler then deduces that
T = Int, but under the hood, it makes function still return
Any and do a cast to
Int. This causes a problem. As the generator of objects thinks it should generate
Any object which afterward is not possible to cast to
MockK as well solves this problem by catching class cast exceptions happening during such casts and making correspondence of points to generate object to casts. This solution works great, but only in case, we specified mocking behavior before using it in tested code. That is why by default mocking in MockK is strict. With small trick, we pushed the problem to appear in quite rare cases.
Next topic is objects/enums and everything that is already created as an instance. As in-lining is employed it is possible to transform any class to become a proxy and attach already created object to a registry of proxies. This is great news for anybody who tried to mock objects in other frameworķs. It is possible to have one instance of an object and just declare it a mock(actually a spy). After such declaration, the same DSL can be employed to set behavior and verify it. As it is a spy all the calls tend to work as if it was original calls, except calls being mocked explicitly.
The intention behind static mocking is quite simple as well. It exists because Kotlin is compiling top level regular and extension functions as static methods. According to the principle MockK is built there was no choice for MockK other than to become a little bit similar to PowerMock, despite the fact that it complicates things a lot. All other advanced functions such as constructor mocking are made on best effort basis, but this one exists because MockK wants to be useful for Kotlin developers.
Next thing is coroutines. Not much to say here, just a way DSL expression can work with suspend functions.
More MockK gained popularity, more it was clear that it is popular among Android developers. Unfortunately, I am not very familiar with the platform. One or two projects where I ever touched it. Anyway, I decided that I need to fight for this category of users. Android tests may be regular JUnit tests running with JDK and Android instrumented tests running in the emulator. The first one has just a few differences that I can check at runtime and use the same code as for non-Android MockK, but second — Android instrumented test required me to put quite a lot of effort to implement(even with the fact that most of the ideas were taken from Dexmaker project). For me it was required even to understand DEX assembly and code C++ to put totally new support for constructor mocking that does not exist in Dexmaker.
The first way to extend MockK is to write new matchers. To do this you need to bear in mind a few things. You build a matcher on top of the standard interface
Matcher . The reason why you need to implement
match function is pretty obvious. But
substitute is not so. Substitute is needed because of multiple runs. Each run some temporary mocks are constructed and there is a need to substitute mocks inside of temporary constructs with permanent structure that is recorded as beheviour at the end.
Even more then that you have a possibility to build compositions out of matchers. There are already standard compositions alike
not . But you are able to build something similiar on your own. Main part here is providing
operandValues to the MockK and ability to set sub-matchers back to a matcher object. All the rest MockK will do automagically based on
MockK is built as a multi-platform project, so allow you to write multi-platform tests(which is good). But among platforms itself, only JVM is implemented.
JS support was experimentally done, but it passes only half of the test suite. There are reasons for that, mainly the absence of some valuable meta-information. Full-blown reflection I think would cover that. Anyway, until the reflection appears it is possible to hack the compiler and provide this meta-information. I have made few attempts, but the speed of compilation of compiler itself is very slow. In the end, this makes all programming hard and time-consuming. Some basic progress I managed to do, but you need to be an expert there to really build something working. And if you become an expert you probably be able to write JS reflection. Anyway I do not have such amount of spare time.
Native support even worse. I was not able to build an experimental prototype. Seems it is even greater hardcore level, that requires to do a compile-time transformation besides meta-information that is not available as well.
Next thing is Android instrumented tests, they are supported for Android API Level 28 and more only. This is due to the fact that JVMTI interface appeared on that level. Good thing is that you do not need to set this level for your program, but rather run an emulator/device with such level. For below levels subclassing is employed and you get some kind of MockK experience, just everything that is possible to implement. And that is not that much. You need to open classes, there is no object mock support, e.t.c.
Hope you enjoy MockK. I spent quite a sufficient effort to create designed from scratch library. It solves few real problems Kotlin developer faces when using other mocking frameworks. I know it is not ideal, as there are bugs and improvement requests. Mostly they are corner cases and usually not easy to fix.
I spend my spare time to support and make it better. So I encourage everybody to do at least simple activity like sharing best practices and answering questions on stack overflow. I realize that contribution is not easy, it requires real involvement and passing the barrier of learning quite complex code, but there are numerous things you can do without diving deeply.