Kotlin Testing With Mocks

2018-06-23

Overview

Mocking is a powerful aid to TDD and ensuring that edge case testing can be done without requiring downstream dependencies.

This is especially valuable when testing interactions with something like a Database.

Mocks also allow for verifying interactions and that parameters are constructed correctly.

The downside is they can create tight coupling between the test and the implementation, thereby reducing the ease of refactoring.

But this article will not focus on proper use of Mocks. The focus is comparing two commonly used mocking frameworks for Kotlin tests.

Mockito

For Java development, Mockito is the most commonly used library. For Kotlin, Mockito works very well BUT a few extra libraries make the interactions much easier.

mockito-inline for mocking final classes

mockito-kotlin that provides some helpers for more idiomatic code, and to address some incompatiblities.

e.g. when -> whenever. issues with any() when parameter is nullable. and adds some helpers.

Using Mockito Extension in JUnit Jupiter, and annotation based mocking results in strict checking. Basically, if no expectations are defined on a mock object, it will act like a stub. This means it will return a reasonable default for any calls.

As soon as an expectation is defined, the Mock becomes strict. It will report any calls that don’t match a defined expectation AND will report any expectations that were defined, but not called. This is like defining verify on every interaction, and adding a verify no more interactions.

There is no option to ensure a mock is actually used as a mock until at least one interaction is defined.

The only way to ensure all mock calls are defined would be to call verify with noMoreInteractions on all mocks at the end of the test.

The strict enforcement does not appear to work if you call mock directly, regardless of whether you’re using the MockitoExtension or not. Either in the init of the class or in an @BeforeEach function. I have not dug in to determine why, but that would be my preferred method of defining as then all variables could be val instead of lateinit var

Mockk

This is a Kotlin specific framework, also written in Kotlin. This provides some advantages as it can fully leverage the power of Kotlin, and provide a more idiomatic approach to mocking.

It contains built-in features for mocking final classes, extension functions, coroutines, constructors, and private functions.

There is even work started to support multi-platform.

Mockk makes a distinction between stubs (relaxed mocks) and mocks (strict). If a mock is defined as relaxed, it will provide a reasonable default for any calls.

A strict mock must be told about any interactions, so if an unexpected call is performed on a strict mock, a very informative error is shown.

Mockk does not automatically perform verification (like Mockito does using the MockitoExtension/Strict Stubbing). The only way to accomplish that is to explicitly define the required verify statements.

Mockk has a Rule/Extension for use with JUnit that allows for annotated parameters. For JUnit Jupiter, the extension also allows for annotation of fun parameters.

My preference is to use val properties and call mockk directly rather than having to use lateinit var and the @Mockk annotations.

I like the explicit definition of stub vs mock of Mockk, it’s knowleddge of Kotlin and the good error messages if an interaction is not defined.

I miss the automatic verification to ensure all interactions are explicitly defined.

Comparison

Mockito

Using MockitoExtension and @Mock annotations provides automatic verification. So if that’s what you want, that’s the way to go.

MockitoExtension w/Jupiter does not allow for injection of Mocks into constructor or methods. It only allows for @Mock annotations, and Strict verification of mocking.

If you want performance, and are ok with vague error messages or no warnings on incorrect mocks, you can use the mock() function instead of annotations

MockK

Mockk seems like a better compromise. Can manually put verification of interactions on mocks, and no lateinit var using mock. Also allows for explicit expectation definition vs returning reasonable default values.

Problem is Mockk seemed to be slower running tests, but both take a bit of warm up for the first test.

MockK extension for Jupiter does allow for injection of mocks in constructor or methods and does standard verification using mock() so any approach results in the same use cases/verifications.

There are tickets open on Mockk to investigate performance, and some suggestions. It’s not a terrible difference, and with any luck, it will be addressed

Conclusion

Overall, I lean to Mockk for any new projects. Explicit definition of stub vs mock, good error messages for unexpected interactions, better JUnit Jupiter support, and good understanding/support for Kotlin out of the box.

I would miss the strict verification of Mockito, but I think that’s a reasonable tradeoff

References

Great article on good practices to adopt for test writing and Kotlin.
https://blog.philipphauer.de/best-practices-unit-testing-kotlin/#comment-3947296380


Comments: