A set of noteworthy uses of MockK (that might come in handy)

Christiaan Schop - Mar 6, 2023

As a Java developer I was used to Mockito and during this time it helped me a lot. It is a library that has been there for us for a long time. Sometimes you added Powermockito to give Mockito some extra 'juice' to overcome some final, static or more private issues, but helpful it is. Having said that, last year I started doing some Kotlin work and I quickly came to know MockK. The use of Unit and the fact that classes/methods are final require that juice even more often in Kotlin. I believe MockK is the answer to just that, so I took out some noteworthy uses of MockK. Hope you enjoy!

Mocking... everything

To serve you an amuse, first a short introduction of how to think about MockK. Maybe the best thing to tell you about what can be mocked with MockK is; for almost everything you can think of is probably a very specific solution created. A simple mock looks a lot like Mockito with; @MockK or mock<>(). However, what about an Object (singleton), Class, Constructor, Enum? Just start typing in your IDE and you will probably find a one-lined MockK solution quickly. Also, the MockK website has many examples.

Quick example of the Enumeration as the least straightforward mockk (in my opinion). Here you use the mockkObject singleton mock of an Object to initiate the Enum mock.

1 2 3
mockkObject(HeavyMetalStrengthEnum.TUNGSTEN) every { HeavyMetalEnum.TUNGSTEN.strenght } returns 999 assertEquals(1, HeavyMetalStrengthEnum.TUNGSTEN.strenght)

InjectMockKs

As a starter, I have chosen a rather basic code example. We can note that our Machine() will automatically detect the @MockK properties by using the @InjectMockKs in combination with the @ExtendWith(MockKExtension::class) annotation. It is designed to look for properties in following order: name -> class -> superclass.

If we change the @InjectMockKs, which only mocks lateinit var & var by default, to @OverrideMockKs, we also override val properties

1 2 3 4 5 6 7 8 9 10 11 12 13
class Machine { lateinit var filterOne: MachineFilter lateinit var filterTwo: MachineFilter } @ExtendWith(MockKExtension::class) class MachineTest { @MockK lateinit var filterOne: MachineFilter @MockK lateinit var filterTwo: MachineFilter @InjectMockKs var machine = Machine() }

Stubbing, but more 'relaxed'

In this test we mock an interface in three ways, showing how a mockk<>() can easily be turned into a stub with default return values. It makes you able to focus only on the behavior you want to specify and focus on. First we see that just a mockk<>() will throw exceptions in both methods in the interface. However, if we add a config relaxed = true to the second mock, both methods run perfectly fine, returning an empty String and Unit respectively. There is also the option to only stub methods returning Unit by adding the config relaxUnitFun = true, which is shown in the last mock.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
interface IFoundryLetsDoThings { fun doStuffAndReturnString(cool: String): String fun doStuffAndReturnUnit(): Unit //`Unit` is not necessary, but to be clear } val foundryMock = mockk<IFoundryLetsDoThings>() assertThrows<MockKException> { foundryMock.doStuff() } //Throws exception assertThrows<MockKException> { foundryMock.doUnitThings() } //Throws exception val foundryStub = mockk<IFoundryLetsDoThings>(relaxed = true) foundryStub.doStuff("thing") foundryStub.doUnitThings() val foundryStubUnit = mockk<IFoundryLetsDoThings>(relaxUnitFun = true) assertThrows<MockKException> { foundryMock.doStuff("thing") } //Throws exception foundryStub.doUnitThings()

Oh so original

Sometimes you might need that only one method is called for real and all others should be stubbed. In the below example we see just that. We mock a class Metal and stub its method, but also tell that for one specific value the original call should be made.

1 2 3 4 5 6 7 8 9
class Metal { fun filterAluminium(name: String): String = if (name.contains("alu")) "found it!" else "nah" } val metal = mockk<Metal>() every { metal.filterAluminium(any()) } returns "no decision" every { metal.filterAluminium("aluminium") } answers { callOriginal() } assertEquals("no decision", metal.filterAluminium("alu")) assertEquals("found it!", metal.filterAluminium("aluminium"))

Capturing machine(s)

In the next examples we see MockK's Capture at work (in Mockito this is ArgumentCaptor), capturing arguments. The subject here is a MachineService that injects a MachineRepository to save a machine. A common scenario, where a service executes, and you want to check what is processed into e.g. repository or third party library.

In following example has two things to mention. The slot, which is used as an argument in capture, which in its turn is the argument in the save method of the repository. The other one being that we return the result by calling slot.captured

1 2 3 4 5 6 7 8 9 10 11
class MachineService(val repository: MachineRepository) { fun saveMachine(machine: String) = repository.save(machine) } val slot = slot<String>() val machine = "capture me please I am a machine" every { repository.save(capture(slot)) } returns Unit service.saveMachine(machine) assertEquals(machine, slot.captured)

You can also catch multiple arguments in one test, which is shown in the next example. The only change to make is that the capture does not receive a slot, but a mutableListOf.

As a small extra I have changed returns Unit to just runs which is quite handy when you do not care about the Unit it returns and want to be explicit about it.

[HINT] If we had a larger list of methods that required stubbing, we now could know a neat answer for this. Rather listing methods with just runs we could also use the mockk<type>(relaxed = true)

1 2 3 4 5 6 7 8 9
val list = mutableListOf<String>() val machine = "capture me please I am a machine" every { repository.save(capture(list)) } just runs machineService.saveMachine(machine) machineService.saveMachine(machine) assertEquals(machine, list[0]) assertEquals(machine, list[1])

Coroutines

With Kotlin coroutines and project Loom peeking around the corner I definitely wanted a coroutine MockK example for dessert.

We created a FoundryService with a repository and a cache in its constructor. We use suspend functions, which can only live in a coroutine. We use the faker lib to create us some quality mock data and initialize the service. With coEvery we can instruct coroutine mocks and tell it to return the foundry. We start the main function inside a runBlocking coroutine scope. In my opinion, blocking is best practice when it comes to unit testing, it mitigates the asynchronous of things. Making it more reliable and often less flaky in pipelines. Lastly we can verify suspended results with coVerify.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class FoundryService( private val foundryCache: FoundryCache, private val foundryRepository: FoundryRepository ) { suspend fun getCurrentCustomer(): Foundry { val company = foundryCache.getCurrentCompany() //suspend function return foundryRepository.getFoundryByName(company.name) //suspend function } } val faker = Faker() val foundry = Foundry( name = faker.company.name(), //Effertz, Heathcote and Goyette address = faker.address.fullAddress() //Suite 884 55298 Olin Lodge, Davisburgh, CT 69233 ) val foundryCache: FoundryCache = mockk() val foundryRepository: FoundryRepository = mockk() val service = FoundryService(foundryCache, foundryRepository) coEvery { foundryCache.getCurrentCompany() } returns foundry runBlocking { //Coroutine scope service.getCurrentCustomer() } coVerify { foundryRepository.getFoundryByName(foundry.name) }

[Hint] If you like the faker library as much as I do, you could read the Spice up your tests using Faker blog!

Final remarks

Whereas tests in general help to understand code and frameworks quicker, neat and easy to understand tests increase this process. MockK definitely tries to help us with this. I feel like the syntax can be interpreted more naturally and with the right variables it just flows, almost like complete sentences.

Although the examples could have been much juicier with exotic examples, I hope these uses are readily applicable to your work with Kotlin tests.

Thanks for reading!