What you'll learn
Professional code is tested code. Automated tests catch regressions, document intent, and let you refactor without fear. Kotlin has first-class testing tools — this module tours JUnit 5, Kotest, MockK, and how to test coroutines, so you can prove your code works.
By the end you'll be able to:
- Write unit tests with JUnit 5 and assertions
- Understand the expressive Kotest style
- Isolate code from dependencies using MockK
- Test suspending functions and flows, and apply TDD
JUnit 5
JUnit is the JVM's standard test framework and works perfectly with Kotlin. A test is a method annotated @Test that makes assertions. A lovely Kotlin touch: you can name test functions with backticks so they read as plain sentences in the report:
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals
class Calculator {
fun add(a: Int, b: Int) = a + b
}
class CalculatorTest {
private val calc = Calculator()
@Test
fun `adds two numbers`() { // backtick names read like sentences
assertEquals(5, calc.add(2, 3))
}
@Test
fun `handles negative numbers`() {
assertEquals(-1, calc.add(2, -3))
}
}Tip
Kotest
Kotest is a popular Kotlin-native framework with expressive matchers and several testing styles (StringSpec, FunSpec, BehaviorSpec…). Its shouldBe-style assertions read almost like English:
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.collections.shouldContain
class CalculatorSpec : StringSpec({
"adds two numbers" {
Calculator().add(2, 3) shouldBe 5 // fluent, readable matchers
}
"list contains an element" {
listOf(1, 2, 3) shouldContain 2
}
})Mocking with MockK
A unit test should test one unit in isolation. When your class depends on a database, network, or other service, you replace that dependency with a mock — a stand-in whose behaviour you script. MockK is the idiomatic Kotlin mocking library:
import io.mockk.every
import io.mockk.mockk
interface UserRepo { fun findName(id: Int): String }
class Service(private val repo: UserRepo) {
fun greet(id: Int) = "Hello, ${repo.findName(id)}"
}
// In the test — replace the real repo with a controllable fake:
val repo = mockk<UserRepo>()
every { repo.findName(1) } returns "Ada" // stub the behaviour
val greeting = Service(repo).greet(1) // "Hello, Ada"Note
verifythat a method was called with the expected arguments.Testing coroutines
Testing suspending code needs a coroutine context. The kotlinx-coroutines-test library provides runTest, which runs your test in a special scope with a virtual clock — so delay(1000) completes instantly instead of actually waiting a second:
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals
suspend fun loadValue(): Int { delay(1000L); return 42 }
class AsyncTest {
@Test
fun `loads the value`() = runTest { // virtual clock: delays are skipped
assertEquals(42, loadValue()) // finishes instantly, not in 1s
}
}Test-driven development
TDD flips the usual order: write a failing test first, then the minimum code to make it pass, then refactor — the red, green, refactor cycle. It keeps you focused on behaviour and guarantees every line is covered by a test. You don't have to practise TDD strictly, but writing tests early always beats bolting them on later.
Key idea
Recap & quick check
Key takeaways
- Automated tests catch regressions, document intent, and make refactoring safe.
- JUnit 5 is the JVM standard; Kotlin's backtick names make test reports read like sentences.
- Kotest offers Kotlin-native styles and fluent shouldBe matchers.
- MockK replaces real dependencies with scripted fakes so you can test a unit in isolation.
- runTest from kotlinx-coroutines-test uses a virtual clock, so coroutine delays don't slow tests.
Quick check
1. What marks a method as a JUnit test?
2. Why use a mock in a unit test?
3. What does runTest do for coroutine tests?
4. What is the red-green-refactor cycle?
5. What should a good test focus on?
Tested code is trustworthy code. Next we'll learn how Kotlin projects are actually built and assembled with Gradle and the Kotlin ecosystem.