Testing

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Kotlin testing builds directly on the JVM testing ecosystem covered in Testing with JUnit 5 and Mockito — JUnit 5 runs Kotlin test classes with no changes needed — plus two Kotlin-specific additions: kotlin.test and MockK, and coroutine-aware test support.

kotlin.test

kotlin.test is a thin, multiplatform-friendly facade over whichever underlying test framework is on the classpath (JUnit 4, JUnit 5, or a JS/Native test runner for a Kotlin Multiplatform project) — useful when test code itself needs to stay platform-agnostic, though a JVM-only project commonly just uses JUnit 5’s own org.junit.jupiter.api.Assertions directly instead:

import kotlin.test.Test
import kotlin.test.assertEquals

class CalculatorTest {
    @Test
    fun addsTwoNumbers() {
        assertEquals(5, Calculator().add(2, 3))
    }
}

JUnit 5 with Kotlin

Backtick-escaped test names (Lexical Structure and Style) are the single most visible Kotlin-specific idiom in test code — a descriptive sentence as the method name, with no separate @DisplayName needed:

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertThrows

class AccountTest {
    @Test
    fun `withdraw reduces the balance`() {
        val account = Account(100)
        account.withdraw(30)
        assertEquals(70, account.balance())
    }

    @Test
    fun `withdrawing more than the balance throws`() {
        val account = Account(100)
        assertThrows(IllegalStateException::class.java) {
            account.withdraw(999)
        }
    }
}

@ParameterizedTest, @BeforeEach/@AfterEach, and every other JUnit 5 Jupiter feature from Testing with JUnit 5 and Mockito apply unchanged.

MockK

MockK is a mocking library built for Kotlin’s language features (top-level/extension functions, coroutines, object singletons) that Mockito — built for Java — cannot mock directly:

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify

class OrderServiceTest {
    @Test
    fun `totals an order`() {
        val pricing = mockk<PricingClient>()
        every { pricing.unitPrice("sku-1") } returns 250

        val service = OrderService(pricing)
        val total = service.total("sku-1", quantity = 3)

        assertEquals(750, total)
        verify(exactly = 1) { pricing.unitPrice("sku-1") }
    }
}

MockK can also mock a suspend function directly (coEvery { …​ } returns …​ / coVerify { …​ }) and, with mockkObject, a Kotlin object singleton — both scenarios Mockito has no native way to handle.

kotlinx-coroutines-test for Testing Suspend Functions and Flows

Testing a suspend function or a Flow (Coroutines Basics, Flows) needs a coroutine to call it from, plus control over virtual time so a test with delay(5_000) inside it does not actually take five seconds to run. runTest provides both:

import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.delay

@Test
fun `fetchUser returns the expected user`() = runTest {
    val result = fetchUser(1)                 // "runTest" provides a TestScope to call suspend functions from
    assertEquals("Ada", result.name)
}

@Test
fun `retries after a delay`() = runTest {
    val start = currentTime                    // virtual time, not wall-clock time
    delay(5_000)                                // completes "instantly" -- runTest auto-advances virtual time
    assertEquals(5_000, currentTime - start)
}

@Test
fun `emits the expected values`() = runTest {
    val values = numbers().toList()             // collecting a Flow into a List for straightforward assertions
    assertEquals(listOf(1, 2, 3), values)
}

runTest is the direct, coroutine-aware replacement for runBlocking in test code — using plain runBlocking in a test with real delay() calls would make the suite slow for no benefit, since nothing about the delay itself is under test.

See Also