Mocking is a process that is used in unit testing when the unit being tested has external dependencies. For example, a code that initiates the downloading of an image (and finally conveys success-failure on the UI) will have a dependency on a NetworkModule. While unit testing this code, the NetworkModule should be mocked. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies. So in simple words, mocking is creating objects that simulate the behavior of real objects.
In Android, there are a lot of frameworks used for mocking in unit testing, such as PowerMock, Mockito, EasyMock, etc. MockK is definitely a better alternative to other mocking frameworks for Kotlin, the official development language for Android. Its main philosophy is first-class support for Kotlin features.
Mockito Framework and its Shortcomings
I started off with adding the Mockito dependency to my Kotlin project and wrote a simple unit test case.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.13.0</version>
</dependency>
class DatabaseTest {
class GenerateRecord { fun generate(): String = "Random String" }
class Dao { fun insert(record: String) = println("""Inserting "$record"""") }
class Service(private val generator: GenerateRecord, private val dao: Dao) {
fun calculate() {
val record = generator.generate()
dao.insert(record)
}
}
val generator = Mockito.mock(GenerateRecord::class.java)
val dao = Mockito.mock(Dao::class.java)
val service = Service(generator, dao)
@Test
fun insertRecordTest() {
val mockedRecord = "mocked String"
Mockito.`when`(generator.generate()).thenReturn(mockedRecord)
service.calculate()
Mockito.verify(generator).generate()
Mockito.verify(dao).insert(mockedRecord)
Mockito.verifyNoMoreInteractions(generator, dao)
}
}
When you ran it, It gives you this nice error:
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class GenerateRecord
Mockito cannot mock/spy because :
- final class
— anonymous classes
— primitive types
As all classes and methods are final by default in Kotlin, using Mockito appears to be a bit problematic due to how Mockito creates its mocks. You would have to explicitly make your classes inheritable using the open modifier. Another approach would be to add interfaces to everything.
Starting from Mockito version 2.0.0, it did become possible to mock final classes (although it is an incubating, opt-in feature). This, however, requires a bit of a setup really.
The Idiomatic Mocking Framework for Kotlin
MockK’s main philosophy is offering first-class support for Kotlin features and being able to write idiomatic Kotlin code when using it. Adding MockK is as simple as ever; you only have to add the dependency to your project, and you are set to go.
testCompile "io.mockk:mockk:${mockkVersion}"
class DatabaseTest {
class GenerateRecord { fun generate(): String = "Random String" }
class Dao { fun insert(record: String) = println("""Inserting "$record"""") }
class Service(private val generator: GenerateRecord, private val dao: Dao) {
fun calculate() {
val record = generator.generate()
dao.insert(record)
}
}
val generator = mockk<GenerateRecord>()
val dao = mockk<Dao>()
val service = Service(generator, dao)
@Test
fun insertRecordTest() {
val mockedRecord = "mocked String"
every { generator.generate() } returns mockedRecord
every { dao.insert(mockedRecord) } just Runs
service.calculate()
verifyAll {
generator.generate()
dao.insert(mockedRecord)
}
}
}
Now it is time to talk about such features as captured arguments and mocking.
Syntax In Mockk
First, you need to create a mock:
val mock = mockk<Type>()
Then, you can stub some calls with argument matchers or regular arguments:
every {mock.call(any(), any())} returns 5
Stubbed mocks can now be used in some tested code and called as regular objects.
mock.call(2, 3)
After testing is done, you can verify calls again with matchers or regular arguments:
verify {mock.call(2, 3)}
That's it for the basics, but there is a lot more. Check out the documentation and examples here.
Capturing
Argument capturing can make your life easier if you need to get a value of an argument in every block or verify a block. Let’s say that we have the following class:
There are two ways to capture arguments: using CapturingSlot<Int> and using MutableList<Int>.
CapturingSlot allows you to capture only one value, so it is simpler to use.
val slot = slot<Int>()
val slot1 = slot<Int>()
val mock = mockk<Addition>()
every { mock.call(capture(slot), capture(slot1)) } returns 5
This creates a slot and a mock. You can set expected behavior following way: in case mock.call is called, then arguments are captured to the slot and 5 is returned.
Now for the code being tested:
mock.call(2, 3)
After executing it, the slot.captured value is equal to the first argument (i.e. 2).
Now you can do some checks. Assert for example:
assertEquals(2, slot.captured)
That is basically it. Working with MutableList is the same, but instead of using a slot in the capture function, MutableList should be used.
val list = mutableList<Int>()
val mock = mockk<Type>()
every { mock.call(capture(list), any()) } returns 5
Conclusion
Mockito felt a bit too Java-ish when using it in a Kotlin project. MockK, being a newer library specifically targeted at Kotlin, felt clean and pleasant to use with excellent documentation.