1. Overview
In this tutorial, we’re going to take a look at some of the basic features of the MockK library.
2. MockK
In Kotlin, all classes and methods are final. While this helps us write immutable code, it also causes some problems during testing.
Most JVM mock libraries have problems with mocking or stubbing final classes. Of course, we can add the “open” keyword to classes and methods that we want to mock. But changing classes only for mocking some code doesn’t feel like the best approach.
Here comes the MockK library, which offers support for Kotlin language features and constructs. MockK builds proxies for mocked classes. This causes some performance degradation, but the overall benefits that MockK gives us are worth it.
3. Installation
Installation is as simple as it can be. We just need to add the mockk dependency to our Maven project:
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
For Gradle, we need to add it as a test dependency:
testImplementation "io.mockk:mockk:1.9.3"
4. Basic Example
Let’s create a service that we’d like to mock:
class TestableService {
fun getDataFromDb(testParameter: String): String {
// query database and return matching value
}
fun doSomethingElse(testParameter: String): String {
return "I don't want to!"
}
}
Here’s an example test that mocks TestableService:
@Test
fun givenServiceMock_whenCallingMockedMethod_thenCorrectlyVerified() {
// given
val service = mockk<TestableService>()
every { service.getDataFromDb("Expected Param") } returns "Expected Output"
// when
val result = service.getDataFromDb("Expected Param")
// then
verify { service.getDataFromDb("Expected Param") }
assertEquals("Expected Output", result)
}
To define the mock object, we’ve used the mockk<…>() method.
In the next step, we defined the behavior of our mock. For this purpose, we’ve created an every block that describes what response should be returned for which call.
Finally, we used the verify block to verify whether the mock was invoked as we expected*.*
5. Annotation Example
It is possible to use MockK annotations to create all kind of mocks. Let’s create a service that requires two instances of our TestableService:
class InjectTestService {
lateinit var service1: TestableService
lateinit var service2: TestableService
fun invokeService1(): String {
return service1.getDataFromDb("Test Param")
}
}
InjectTestService contains two fields with the same type. It won’t be a problem for MockK. MockK tries to match properties by name, then by class or superclass. It also has no problem with injecting objects into private fields.
Let’s mock InjectTestService in a test by using annotations:
class AnnotationMockKUnitTest {
@MockK
lateinit var service1: TestableService
@MockK
lateinit var service2: TestableService
@InjectMockKs
var objectUnderTest = InjectTestService()
@BeforeEach
fun setUp() = MockKAnnotations.init(this)
// Tests here
...
}
In the above example, we’ve used the @InjectMockKs annotation*.* This specifies an object where defined mocks should be injected. By default, it injects variables that are not assigned yet. We can use @OverrideMockKs to override fields that have a value already.
MockK requires MockKAnnotations.init(…) to be called on an object declaring a variable with annotations. For Junit5, it can be replaced with @ExtendWith(MockKExtension::class).
6. Spy
Spy allows mocking only a particular part of some class. For example, it can be used to mock a specific method in TestableService:
@Test
fun givenServiceSpy_whenMockingOnlyOneMethod_thenOtherMethodsShouldBehaveAsOriginalObject() {
// given
val service = spyk<TestableService>()
every { service.getDataFromDb(any()) } returns "Mocked Output"
// when checking mocked method
val firstResult = service.getDataFromDb("Any Param")
// then
assertEquals("Mocked Output", firstResult)
// when checking not mocked method
val secondResult = service.doSomethingElse("Any Param")
// then
assertEquals("I don't want to!", secondResult)
}
In the example, we’ve used the spyk method to create a spy object. We could’ve also used the @SpyK annotation to achieve the same:
class SpyKUnitTest {
@SpyK
lateinit var service: TestableService
// Tests here
}
7. Relaxed Mock
A typical mocked object will throw MockKException if we try to call a method where the return value hasn’t been specified.
If we don’t want to describe the behavior of each method, then we can use a relaxed mock. This kind of mock provides default values for each function. For example, the String return type will return an empty String. Here’s a short example:
@Test
fun givenRelaxedMock_whenCallingNotMockedMethod_thenReturnDefaultValue() {
// given
val service = mockk<TestableService>(relaxed = true)
// when
val result = service.getDataFromDb("Any Param")
// then
assertEquals("", result)
}
In the example, we’ve used the mockk method with the relaxed attribute to create a relaxed mock object. We could’ve also used the @RelaxedMockK annotation:
class RelaxedMockKUnitTest {
@RelaxedMockK
lateinit var service: TestableService
// Tests here
}
8. Object Mock
Kotlin provides an easy way to declare a singleton by using the object keyword:
object TestableService {
fun getDataFromDb(testParameter: String): String {
// query database and return matching value
}
}
However, most of the mocking libraries have a problem with mocking Kotlin singleton instances. Because of this, MockK provides the mockkObject method. Let’s take a look:
@Test
fun givenObject_whenMockingIt_thenMockedMethodShouldReturnProperValue(){
// given
mockkObject(TestableService)
// when calling not mocked method
val firstResult = service.getDataFromDb("Any Param")
// then return real response
assertEquals(/* DB result */, firstResult)
// when calling mocked method
every { service.getDataFromDb(any()) } returns "Mocked Output"
val secondResult = service.getDataFromDb("Any Param")
// then return mocked response
assertEquals("Mocked Output", secondResult)
}
9. Hierarchical Mocking
Another useful feature of MockK is the ability to mock hierarchical objects. First, let’s create a hierarchical object structure:
class Foo {
lateinit var name: String
lateinit var bar: Bar
}
class Bar {
lateinit var nickname: String
}
The Foo class contains a field of type Bar. Now, we can mock the structure in just one easy step. Let’s mock the name and nickname fields:
@Test
fun givenHierarchicalClass_whenMockingIt_thenReturnProperValue() {
// given
val foo = mockk<Foo> {
every { name } returns "Karol"
every { bar } returns mockk {
every { nickname } returns "Tomato"
}
}
// when
val name = foo.name
val nickname = foo.bar.nickname
// then
assertEquals("Karol", name)
assertEquals("Tomato", nickname)
}
10. Capturing Parameters
If we need to capture the parameters passed to a method, then we can use CapturingSlot or MutableList. It is useful when we want to have some custom logic in an answer block or we just need to verify the value of the arguments passed. Here is an example of CapturingSlot:
@Test
fun givenMock_whenCapturingParamValue_thenProperValueShouldBeCaptured() {
// given
val service = mockk<TestableService>()
val slot = slot<String>()
every { service.getDataFromDb(capture(slot)) } returns "Expected Output"
// when
service.getDataFromDb("Expected Param")
// then
assertEquals("Expected Param", slot.captured)
}
MutableList can be used to capture and store specific argument values for all method invocations:
@Test
fun givenMock_whenCapturingParamsValues_thenProperValuesShouldBeCaptured() {
// given
val service = mockk<TestableService>()
val list = mutableListOf<String>()
every { service.getDataFromDb(capture(list)) } returns "Expected Output"
// when
service.getDataFromDb("Expected Param 1")
service.getDataFromDb("Expected Param 2")
// then
assertEquals(2, list.size)
assertEquals("Expected Param 1", list[0])
assertEquals("Expected Param 2", list[1])
}
11. Stubbing Functions Returning Unit
In Kotlin, if a function’s return type is Unit, it means the function doesn’t return anything. It’s pretty similar to Java’s void method.
Let’s add one Unit function to the TestableService class:
fun addHelloWorld(strList: MutableList<String>) {
println("addHelloWorld() is called")
strList += "Hello World!"
}
As the code above shows, when the addHelloWorld() function gets called, it prints a line to the console. Then, the String “Hello World” is added to the given MutableList
In this section, let’s look at how to stub a function returning Unit. Usually, depending on different circumstances, we want to control a Unit function stub in two ways:
- making the function call do nothing, or in other words, skipping the function execution
- calling the real function
So next, we’ll take the addHelloWorld() function as an example and address how to achieve them using MockK.
11.1. Making the Function Do Nothing
There are several ways to skip a Unit function using MockK:
every { addHelloWorld(any()) } returns Unit
every { addHelloWorld(any()) } answers { Unit }
every { addHelloWorld(any()) } just runs
Some of them are not new to us, such as returns … and answers {…}. However, the last one, “every { … } just runs” is easy to understand.
So next, let’s first test if it can skip the original function’s execution and then understand how “just runs” works:
@Test
fun givenServiceMock_whenCallingMethodReturnsUnit_thenCorrectlyVerified() {
// given
val service = mockk<TestableService>()
val myList = mutableListOf<String>()
// when
every { service.addHelloWorld(any()) } just runs
service.addHelloWorld(myList)
// then
assertTrue(myList.isEmpty())
}
As the test above shows, we stub the addHelloWorld() function using “just runs“. And then, we call the function and pass an empty MutableList to it.
If we skip the function’s execution successfully, “Hello World!” shouldn’t be added to the given list after the invocation.
The test passes if we give it a run.
Let’s understand how “just runs” skips the real function call. First, let’s have a look at the just() function’s implementation:
infix fun MockKStubScope<Unit, Unit>.just(runs: Runs) = answers(ConstantAnswer(Unit))
As we can see, it’s an infix function. Therefore, we can write just(runs) in a better readable form: just runs. Further, Runs is a dummy object, and runs is merely a typealias to Runs:
object Runs
typealias runs = Runs
Moreover, if we stub a Unit function as just runs, it calls the answers() function and returns a constant answer: Unit.
11.2. Calling the Original Function
Now, let’s see how to stub a Unit function to make it call the real function. *To call the original function, we can use this approach: every { … } answers { callOriginal() }.*
So next, let’s see how it’s used in a real test:
@Test
fun givenServiceMock_whenCallingOriginalMethod_thenCorrectlyVerified() {
// given
val service = mockk<TestableService>()
val myList = mutableListOf<String>()
// when
every { service.addHelloWorld(any()) } answers { callOriginal() }
service.addHelloWorld(myList)
// then
assertEquals(1, myList.size)
assertEquals("Hello World!", myList.first())
}
The test passes when we run it. So, “Hello World!” is added to myList after calling the function. However, we may ask, if we stub a function and ask it to call the original implementation, why do we bother to stub it?
So next, let’s see an example of when it’s useful.
11.3. When Do We Need callOriginal()?
Let’s say in our test, we want to call the addHelloWorld() function with different parameters. If the list passing to the function contains the string “Kai”, we want to call the actual function. Otherwise, we’d like to skip the function call:
@Test
fun givenServiceMock_whenStubbingTwoScenarios_thenCorrectlyVerified() {
// given
val service = mockk<TestableService>()
val kaiList = mutableListOf("Kai")
val emptyList = mutableListOf<String>()
// when
every { service.addHelloWorld(any()) } just runs
every { service.addHelloWorld(match { "Kai" in it }) } answers { callOriginal() }
service.addHelloWorld(kaiList)
service.addHelloWorld(emptyList)
// then
assertEquals(listOf("Kai", "Hello World!"), kaiList)
assertTrue(emptyList.isEmpty())
}
As we can see, we’ve stubbed the addHelloWorld() function twice, depending on our requirements. Therefore, callOriginal() allows us to decide function stubbings’ behaviors flexibly.
12. Conclusion
In this article, we’ve discussed the most important features of MockK. MockK is a powerful library for the Kotlin language that provides many useful features. For more information about MockK, we can check the documentation on the MockK website.
As always, the sample code presented is available on GitHub.