1. Introduction
There is no doubt, that testing is really crucial in the development process of any project. It allows us to automate the process of checking the functionality, as well as improves the quality of the code.
One of the techniques commonly used in unit testing is mocking. To put it in simple terms, mock objects are the objects that simulate the behavior of real objects.
In this article, I’d like to show you how to use MockK– an open-source mocking library for Kotlin- with JUnit 5.
2. Prepare the Code For Testing
Before we will head to the testing part, let’s write the code, which we will be testing later:
class ExampleClass { fun multiplyByTen(number: Int) = 10 * number fun publicFunction() = privateFunction() private fun privateFunction() = "Returned value" } class Injected { fun multiplyByFive(number: Int) = 5 * number } class ExampleClassWithDependency { lateinit var injected: Injected fun returnInjectedValue(number: Int) = injected.multiplyByFive(number) } object ExampleObject { fun concat(one: String, two: String) = one + two }
As you can see, we’ve created a few simple classes and one object. Just for the record, a Kotlin object is a special singleton class- a class that has got only one instance.
3. Imports
As the next step, let’s add the required dependencies to our project:
implementation("io.mockk:mockk:1.10.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.7.0")
3. Using MockK library
In this article, we will learn how to use a few simple features of the library. If you would like to learn more, I highly recommend checking out MockK’s Github repository.
3.1. Mocking
Firstly, let’s see how to specify what exactly should be returned by the function of the mocked class:
@Test fun `Mock a class`() { val exampleClass = mockk() every { exampleClass.publicFunction() } returns "Mocked value" val result = exampleClass.publicFunction() assertEquals("Mocked value", result) }
With this code, the “Mocked value” String will be returned for each invocation of the publicFunction.
Nextly, let’s mock a property of the class:
@Test fun `Mock a property of the class`() { val injected = mockk() val exampleClass = ExampleClassWithDependency() exampleClass.injected = injected every { injected.multiplyByFive(any()) } returns 7 val result = exampleClass.returnInjectedValue(10) assertEquals(7, result) }
This time, our result will be 7, even though the original function should return 50.
3.2. Relaxed mock
What happens if we do not specify the behavior of the invoked function? Let’s check this code:
@Test fun `Example of MockKException`() { val exampleClass = mockk() exampleClass.publicFunction() }
The test failed throwing MockKException informing us that no answer has been found for the function we were trying to test.
@Test fun `Fix MockKException using relaxed mock`() { val exampleClass = mockk(relaxed = true) val defaultStringValue = "" assertEquals(defaultStringValue, exampleClass.publicFunction()) }
To fix that, we can either specify explicitly the behavior or use a relaxed mock. A relaxed mock returns the default values for all the functions within the mocked class. As we can see above, the default String value returned by the function will be an empty String.
3.3. Using Annotations
If we would like to simplify the creation of the object, we can use annotations. As we are using JUnit5, we need to annotate our test class with @ExtendWith:
@ExtendWith(MockKExtension::class) internal class ExampleClassTest
As the next step, let’s add two properties to it:
@MockK lateinit var annotatedMock: Injected @InjectMockKs var annotatedClass = ExampleClassWithDependency()
And create a new test:
@Test fun `Simplified property mock with annotation`() { every { annotatedMock.multiplyByFive(any()) } returns 7 val result = annotatedClass.returnInjectedValue(10) assertEquals(7, result) }
With this approach, we eliminate the need to manually create the mock for each test.
3.4. Spy The Object
Sometimes, we would like to have the possibility to check the real behavior of the class, as well as a mocked one. For that case, we can use a spy:
@Test fun `Spy a class`() { val exampleClass = spyk() assertEquals("Returned value", exampleClass.publicFunction()) }
As we can see here, the function returns the value that has been implemented in our code.
3.5. Mock Private Function Behavior
In our class implementation, publicFunction returns the value from privateFunction. Let’s check, how we can specify the behavior of the private function:
@Test fun `Mock a private function`() { val exampleClass = spyk(recordPrivateCalls = true) every { exampleClass["privateFunction"]() } returns "Mocked value" assertEquals("Mocked value", exampleClass.publicFunction()) }
3.6. Mock an Object
As we’ve told in the beginning, the Kotlin object is a special singleton class. To mock it, we will use mockkObject:
@Test fun `Mock an object`() { mockkObject(ExampleObject) every { ExampleObject.concat(any(), any()) } returns "Mocked value" val result = ExampleObject.concat("", "") assertEquals("Mocked value", result) }
Even though Kotlin allows us to have only one instance of the object class, the MockK library allows us to create multiple instances of its mocks:
@Test fun `Multiple mocked instances of object`() { val firstMock = mockk() val secondMock = mockk() every { firstMock.concat(any(), any()) } returns "One" every { secondMock.concat(any(), any()) } returns "Two" val firstResult = firstMock.concat("", "") val secondResult = secondMock.concat("", "") assertEquals("One", firstResult) assertEquals("Two", secondResult) }
3.7. Capturing
In our previous examples, we didn’t care about what arguments were passed to the stubbed methods. A technique called capturing allows us to validate what arguments has been passed:
@Test fun `Capture passed value`() { val exampleClassMock = mockk() val argumentSlot = slot() every { exampleClassMock.multiplyByTen(capture(argumentSlot)) } returns 5 exampleClassMock.multiplyByTen(55) assertEquals(55, argumentSlot.captured) }
As we can see, the slot contains the captured value, which we have passed to our checked function.
3.8. Verification
If we would like to check how many times the function has been invoked, we can use verify:
@Test fun `Verify calls`() { val exampleClassMock = mockk() every { exampleClassMock.multiplyByTen(any()) } returns 5 exampleClassMock.multiplyByTen(10) exampleClassMock.multiplyByTen(20) verify(exactly = 2) { exampleClassMock.multiplyByTen(any()) } confirmVerified(exampleClassMock) }
In the above example, we’ve used the exactly parameter to verify that the behavior happened exactly 2 times. If we would specify another number, the test would fail and inform us, that the verification failed.
4. Summary
And that would be all for this article. We’ve learned how to use some basic features of the MockK library with JUnit5.
For the source code of the project, please visit our project on GitHub.
If you enjoyed this tutorial, I would be forever grateful if you would like to share some feedback with me through our page, group, or contact form. I highly appreciate all the comments and suggestions that I am getting from you.