The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Unit testing is an age-old integral part of the software engineering practice. Most successful software products use properly written unit tests; fundamentally, unit testing verifies the correctness of a piece of code.
Writing code to test code may seem counterintuitive, but it is an art unto itself that gives a developer confidence in their code.
Below are the significant benefits we derive from writing unit tests:
The Kotlin programming language is fundamentally executed in the JVM environment. Its concise and fancy language features have made it popular within the community, and it is being used frequently in projects such as Android applications, Kotlin Multiplatform Mobile (KMM) apps, and spring boot applications.
There are currently two popular frameworks built to aid in effective unit testing: Mockito and Mockk. In this post, weâll talk about each of them through the following sections:
Mockk and Mockito are libraries that help write unit tests that target JVM platforms. Mockito has been around since the early days of Android development and eventually became the de-facto mocking library for writing unit tests.
Mockito and Mockk are written in Java and Kotlin, respectively, and since Kotlin and Java are interoperable, they can exist within the same project. Essentially, both libraries can be used interchangeably in these projects, but the preeminence of Kotlin has tipped the balance in favor of Mockk for most Kotlin projects.
The following points summarize why Mockk is favored over Mockito for Kotlin projects:
For the purpose of this article, we will implement a simple user repository class to demonstrate writing unit tests in a Kotlin project. There are two things to note before we proceed:
Enough talk, let us get our hands dirty!
First, define the interface for the repository like so:
interface UserRepository {
suspend fun saveUser(user: User)
suspend fun getUser(id: String): User
suspend fun deleteUser(id: String)
}
This is basically a contract that will be implemented by the concrete class. See the code block below.
class UserRepositoryImpl constructor(
private val dataSource: DataSource
) : UserRepository {
override suspend fun saveUser(user: User) {
dataSource.save(user)
}
override suspend fun getUser(id: String): User {
return dataSource.get(id) ?: throw IllegalArgumentException("User with id $id not found")
}
override suspend fun deleteUser(id: String) {
dataSource.clear(id)
}
}
UserRepositoryImpl has a dependency on DataSource, through which it fulfills the contract by UserRepository.
DataSource is a simple Kotlin class. Its purpose is to store user data in memory, where it can later be retrieved. See the code block below for details:
class DataSource {
private val db = mutableMapOf<String, User>()
fun save(user: User) = db.let { it[user.email] = user }
fun get(key: String): User? = db[key]
fun clear(key: String) = db.remove(key)
fun clearAll() = db.clear()
}
To keep things simple, I have used a mutableMap object to save a User to memory. Maps are collections that holds pairs of objects (keys and values), so it makes sense to have the user email serve as the unique key for saving and retrieving the User.
<h3=âadd-library-dependencies-gradleâ>Add library dependencies to Gradle
Add the following dependencies to your app-level Gradle file, like so:
//Mockk testImplementation "io.mockk:mockk:1.12.4" //Mockito testImplementation "org.mockito:mockito-core:4.0.0" testImplementation "org.mockito:mockito-inline:4.0.0" testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
Using Mockito in a Kotlin project requires some extra dependencies for the following reasons:
:mockito-inline:. You might think an alternative would be to add the open modifier to the class involved, but this is not recommended because it will mess up your code base and force you to define your classes as open:mockito-kotlin is a library that provides helpful functions for working with Mockito in Kotlin projectsimport io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.slot
import java.util.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class UserRepositoryImplTest {
private val dataSource = mockk<DataSource>(relaxed = true)
private val sut = UserRepositoryImpl(dataSource)
@Test
fun `verify correct user params are used`() = runTest {
val user = buildUser()
sut.saveUser(user)
val captor = slot<User>()
coVerify { dataSource.save(capture(captor))}
Assert.assertEquals(user.email, captor.captured.email)
}
@Test
fun `verify correct user is retrieved`() = runTest {
val email = "[email protected]"
coEvery { dataSource.get(any()) } returns buildUser()
val user = sut.getUser(email)
Assert.assertEquals(email, user.email)
}
@Test
fun `verify user is deleted`() = runTest {
val email = "[email protected]"
sut.deleteUser(email)
coVerify { dataSource.clear(any()) }
}
companion object {
fun buildUser() = User(
id = UUID.randomUUID().toString(),
email = "[email protected]",
fullName = "Emmanuel Enya",
verificationStatus = User.VerificationStatus.Verified,
memberShipStatus = User.MemberShipStatus.Free
)
}
}
The above code block is a fairly simple test class with minimal test cases. At the top level of the class body, I have mocked the data source and created an instance of the system under test.
Notice that DataSource is mocked with relax set to true, like so:
mockk<DataSource>(relaxed = true)
This kind of mock returns a simple value for all functions, allowing you to skip specifying behavior for each case. Check the Mockk documentation for more details on relaxed mocks.
Other sections of the code block will be examined side-by-side with the Mockito variant of the test class.
import java.util.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito.*
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
@OptIn(ExperimentalCoroutinesApi::class)
class UserRepositoryImplTestMockito {
private val dataSource = mock(DataSource::class.java)
private val sut = UserRepositoryImpl(dataSource)
@Test
fun `verify correct user params are used`() = runTest {
val user = buildUser()
sut.saveUser(user)
val captor = argumentCaptor<User>()
verify(dataSource).save(captor.capture())
Assert.assertEquals(user.email, captor.firstValue.email)
}
@Test
fun `verify correct user is retrieved`() = runTest {
val email = "[email protected]"
`when`(dataSource.get(any())).then { buildUser() }
val user = sut.getUser(email)
Assert.assertEquals(email, user.email)
}
@Test
fun `verify user is deleted`() = runTest {
val email = "[email protected]"
sut.deleteUser(email)
verify(dataSource).clear(any())
}
companion object {
fun buildUser() = User(
id = UUID.randomUUID().toString(),
email = "[email protected]",
fullName = "Emmanuel Enya",
verificationStatus = User.VerificationStatus.Verified,
memberShipStatus = User.MemberShipStatus.Free
)
}
}
The above code block is a test class written with Mockito. At the top level of the class body, we have the mocked dependency and the SUT set up in a similar fashion to how we did with Mockk.
However, one salient point to note is that there is no relaxed mock argument. The reason for this is because Mockito provides default answers for behaviors when they are not stubbed.
I feel Mockk shines here because mocks are not relaxed by default, which encourages you to have total control over the functions call being made by the SUT.
argumentCaptor is a function from the mockito-kotlin extension library. It helps to capture a single argument from the mocked object, usually done in the verification block.
The Mockk variant is a slot.
Usually, when writing unit tests, we specify answers to function calls on mocked objects. This is referred to as stubbing in unit testing.
Using Mockito, it is declared like so:
`when`(dataSource.get(any())).then { buildUser() }
Mockito does not have inbuilt support for Kotlin coroutines, which means testing coroutines will require the use of runTest for the code to compile.
In Mockk, there is coEvery { } for stubbing coroutines and every { } for regular functions. This distinction adds clarity to the code being tested.
An important part of unit testing is to verify the method interactions of mocked objects in production code.
suspend functions can only be invoked by other suspend functions. Having coVerify{ } in place gives developers the support to stub suspend functions, which ordinarily wouldnât be possible with the verify{ } block.
Mockk provides two functions to test suspend functions and regular functions:
coVerify { }, for suspend functionsverify { }, for regular functionsMockito uses verify() for verification and still requires using runTest to support suspend functions. This still works, but is less clear than Mockkâs approach.
Weâve explored unit testing in Kotlin projects and how to effectively write tests with Mockk and Mockito.
Feel free to use whichever library you find attractive, but I would recommend Mockk because of how flexible it is when working with the Kotlin language.
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps â try LogRocket for free.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more â writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Would you be interested in joining LogRocket's developer community?
Join LogRocketâs Content Advisory Board. Youâll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now