How To Write Human-Readable Tests in Kotlin With Kotest and MockK
The complete toolbox for writing tests in Kotlin that are easy to comprehend and maintain

In all likelihood, everybody who has ever worked with Java knows about Mockito and Hamcrest. These frameworks help immensely when testing code by isolating dependent components and writing assertions, but they don’t do us a lot of favors regarding readability.
In this article, I want to discuss why I decided to switch to using Kotest and MockK after I left Java for Kotlin.
Where We Come From
Let’s have a look at what a regular and simple unit test looks like using Mockito and Hamcrest:
What’s used in this snippet:
- An initialization method for creating our mocks for two dependent-on services.
- Assigning a mock response for a method.
- Creating a captor to verify passed arguments.
- Assertions about the number of calls to a method.
Our code is generally not as readable “from left to right” (or vice versa) as we’re used to. Even if we are regularly in touch with that syntax, the cognitive load is still higher. We should always keep in mind that one of the most important aspects of a unit test is that it’s easy to read and comprehend. That means that every bit of reduced effort to know what’s going on is worth integrating.

If we phrase the verify
assertion of our example into a sentence, we see that there’s some mix-up in comparison to the actual code. Yes, we could rephrase that into something more fitting, but I’m dividing the conditions that have to be fulfilled (verify
and times
= our method should be called once) and the target (services.add
).
Looking at Hamcrest’s assertThat
assertion, we can already see some improvement, but it’s still not perfect.
Let’s dive into MockK and Kotest to learn about how we can further improve this.
MockK for Mocking
MockK offers all the features we know from using Mockito, but with better readability and Kotlin compatibility.
Mocking
When creating your mock objects to isolate your system under test, there’s no huge difference to Mockito from a syntax perspective. The interesting and distinguishing part is how we define our stub responses with every
.
The default mock requires you to specify responses for all receiving calls. If there’s a call but no stubbed response, the test will fail (e.g. calling any other method than add
would end up with failed test in the example). If you want to explicitly ignore those, you can create a relaxed mock.

Looking at the readability of the method’s stub, we see its greatness.
Spying
Working with spies is equally easy.
verify
also looks a lot cleaner now. The class and method are bound together and the number of expected calls is right next to the assertion keyword itself.

For me, MockK’s syntax has a lot less “noise” and is easier to grasp.
Capturing
Validation of passed arguments by capturing is done either via CapuringSlot
or MutableList
. The first option is for easier matching of a single call.
Spring support
As a Kotlin developer, there’s a good chance that you’re also using Spring Boot and wondering about MockK’s compatibility with it. springmock provides compatibility with Spring Boot’s integration tests.
It provides all the same functionality as the Mockito-based Spring Boot mock beans. Just start using @MockkBean
and @SpykBean
.
Note: This is a non-exhaustive list of MockK’s feature set. There’s much more to explore on mockk.io. Also, Oleksiy Pylypenko wrote a great comprehensive series about MockK’s features.
Kotest for Assertions
Kotest enables us to write assertions in a simple and clean way.
Matchers
Kotest brings a large list of core matchers with a syntax that is similar to JavaScript’s Jest. Matchers can be used either as an extension or as an inflix function.
Extension functions have the advantage of the auto-completion feature from your favorite IDE. I still prefer using the inflix style due to its strict separation of actual and expected value.
Exceptions
Checking for exceptions and their validations can be done with shouldThrow
. You can either only verify that the exception is thrown or also do additional checks on the caught exception.
Clues
Immediately knowing what caused the error is also really important in order to not be frustrated when dealing with failed tests. Kotest helps with that by introducing withClue
, which enables you to add further details to your assertions or avoid confusing assertion messages due to null values.
Kotest is not only a library for assertions but also for property testing. Further, it offers a lot more features not described in this article. I highly recommend browsing its documentation on kotest.io.
Putting It All Together
Let’s update our initial example:
That already looks much better, even though it’s only a very simple example. For tests with a much more complex stubbing setup and assertions, using MockK and Kotest has an even greater impact on how fast you can comprehend tests you wrote a very long time ago or that were written by somebody else.
Key Takeaways
Tests are designed to break regularly, so you know that you’ve likely caused unintended side effects — that’s why you’ll read them a lot. With MockK and Kotest, you get the complete tooling you need for writing tests in Kotlin with great readability.
And as always, don’t just take my opinion. Try it out yourself today.
Thank you for reading.