skip to content

Testing

Testing in Kotlin specifically: the multiplatform kotlin.test API, MockK for mocking, Kotest for spec-style and property-based tests, and the machinery for testing coroutines and Flow deterministically. The coroutine half is where interviews get demanding.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

107 · 6 sections

What is the kotlin.test library, and how do you write a basic assertion-based unit test with it?

level: juniorimportance: must knowfreq 70%
basics
~10 s

kotlin.test is Kotlin's small built-in testing toolkit. You mark a function with @Test, then call assertions like assertEquals or assertTrue to check that your code produced the right result.

open as a page

How does MockK let you create and verify mocks in Kotlin, and why is it preferred over Mockito for Kotlin code?

level: middleimportance: must knowfreq 68%
basics
~20 s

MockK is a Kotlin mocking library. You create a fake object with mockk(), tell it what to return using every { ... } returns ..., call your code, then check the fake was used with verify { ... }.

open as a page

What does runTest from kotlinx-coroutines-test do, and why is it needed for deterministic coroutine tests?

level: seniorimportance: must knowfreq 58%
basics
~10 s

runTest runs your suspend code in a special test environment that skips real waiting. Delays are fast-forwarded instead of actually sleeping, so coroutine tests finish instantly and give the same result every run.

open as a page

What is Kotest, and what does its spec-style DSL give you over plain kotlin.test?

level: middleimportance: should knowfreq 50%
basics
~10 s

Kotest is a richer Kotlin testing framework. Instead of @Test methods it lets you describe tests in readable blocks (like 'describe / it'), and it ships many ready-made assertions called matchers.

open as a page

How do you deterministically test a Kotlin Flow, including hot flows like StateFlow that never complete?

level: seniorimportance: should knowfreq 44%
basics
~20 s

For a normal flow you collect its values into a list inside runTest and check them. For a flow that never ends, like StateFlow, you use a tool such as Turbine to read items one at a time and then stop collecting.

open as a page

In kotlin.test, how do you assert that a block of code throws a specific exception type, and what does the assertion return?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Use assertFailsWith with the expected exception type and a lambda holding the code. The test passes only if that exception is thrown, and the call hands you back the caught exception so you can check it.

open as a page

What does kotlin.test's assertEquals do, and why does the order of its first two arguments matter?

level: juniorimportance: must knowfreq 80%
basics
~20 s

assertEquals checks that two values are equal and fails the test if they are not. You pass the expected value first and the actual value second; the order changes the failure message, not the pass/fail result.

open as a page

How do you write a JUnit 5 test method with a human-readable name in Kotlin, and why is this idiomatic?

level: juniorimportance: must knowfreq 70%
basics
~10 s

In Kotlin you can name a function using backticks, so you write the test name as a normal sentence with spaces. JUnit then shows that readable name in the test report.

open as a page

In kotlin.test, which annotations mark a test method and its setup/teardown in common (multiplatform) code, and why aren't you using JUnit's @Test directly?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Use @Test to mark a test, @BeforeTest to run setup before each test, and @AfterTest to run cleanup after each. They come from kotlin.test so the same code works on every platform, not just the JVM.

open as a page

Show how to use the value returned by assertFailsWith to assert on an exception's message and its cause.

level: middleimportance: must knowfreq 60%
basics
~10 s

Capture the returned exception in a variable. Then check its message with assertEquals or assertContains, and check its cause by reading ex.cause, asserting its type and message too.

open as a page

How do you create a mock with MockK and make one of its methods return a fixed value?

level: juniorimportance: must knowfreq 85%
basics
~10 s

Create the fake object with mockk(), then tell it what to return using every { obj.method() } returns value. After that, calling the method gives back the value you set.

open as a page

You have a mock with a suspend function and you write every { repo.load() } returns data — it won't compile. Why, and what do you use instead?

level: juniorimportance: must knowfreq 75%
basics
~10 s

every can only call ordinary functions. A suspend function must be called from a coroutine, so MockK gives you coEvery, which can run suspend calls inside its stub block. Use coEvery for suspend functions.

open as a page

In MockK, what does creating a mock with mockk<T>(relaxed = true) do, and how does it differ from a plain mockk<T>()?

level: juniorimportance: must knowfreq 70%
basics
~10 s

A relaxed mock auto-answers every call with a default value, so you don't have to stub each method. A plain mock throws if you call a method you never stubbed.

open as a page

How do you use mockkObject to stub a method on a Kotlin object (singleton) or companion object in a MockK test?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Call mockkObject(MyObject) to turn the real singleton into a spy, then use every { MyObject.foo() } returns ... to fake a method. Clean up afterwards with unmockkObject or unmockkAll.

open as a page

In MockK, how do you assert that a mocked method was called a specific number of times, and what does verify { ... } default to when you don't pass a count?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Use verify(exactly = n) { mock.method() } to require exactly n calls. A plain verify { ... } with no count means "at least one call" (atLeast = 1), so it fails only if the call never happened.

open as a page

What is Kotest's withData and why would you use it instead of writing a separate test for each input?

level: juniorimportance: must knowfreq 55%
basics
~10 s

withData runs the same test body once for each value you give it. Instead of copy-pasting a test many times, you list the inputs and Kotest creates a separate test for each one automatically.

open as a page

What is Kotest's matcher DSL, and how do you assert that a value equals an expected value using shouldBe and shouldNotBe?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Kotest lets you write checks like normal English. You write 'result shouldBe 5' to say result must equal 5, and 'result shouldNotBe 0' to say it must not equal 0. If the check fails, the test fails with a clear message.

open as a page

What is property-based testing in Kotest, and how does forAll differ from a traditional example-based test?

level: juniorimportance: must knowfreq 55%
basics
~10 s

Instead of hand-picking a few inputs, Kotest generates many random inputs and checks that a rule (a property) holds for all of them. forAll runs your check against hundreds of generated values automatically.

open as a page

In Kotest, how do you choose which test-layout style your test file uses, and name the four common spec styles?

level: juniorimportance: must knowfreq 70%
basics
~10 s

You pick a style by which class your test class extends. Common ones are StringSpec, FunSpec, DescribeSpec, and BehaviorSpec. Each just changes how you write and group your tests.

open as a page

Explain how Kotest's table(), row(), and forAll work together for table-driven testing. Show a multi-column example.

level: middleimportance: must knowfreq 50%
basics
~10 s

You build a table from rows, where each row holds several values (the inputs and expected output). Then forAll runs your check once per row, passing that row's values into your lambda.

open as a page

Why should a class that launches coroutines take a CoroutineDispatcher as a constructor parameter instead of hardcoding Dispatchers.IO or Dispatchers.Default?

level: juniorimportance: must knowfreq 70%
basics
~10 s

So tests can pass in a fake, controllable dispatcher. If the dispatcher is hardcoded, the test cannot speed up or control timing and must wait for real threads, making tests slow and flaky.

open as a page

In kotlinx-coroutines-test, what are StandardTestDispatcher and UnconfinedTestDispatcher, and how do they differ in when they execute a newly launched coroutine?

level: juniorimportance: must knowfreq 62%
basics
~10 s

Both are test dispatchers using fake time. StandardTestDispatcher waits: a new coroutine just gets queued until you advance time. UnconfinedTestDispatcher is eager: it runs the new coroutine immediately until its first suspension point.

open as a page

What is runTest and why do you use it to test suspend functions instead of runBlocking?

level: juniorimportance: must knowfreq 80%
basics
~10 s

runTest is a helper from kotlinx-coroutines-test that runs a suspend test body. It skips delays using a fake clock, so tests with delay() finish instantly instead of really waiting.

open as a page

What is virtual time in coroutine tests, and why does a delay(10_000) inside runTest finish almost instantly instead of taking 10 seconds?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Coroutine tests use a fake clock. Instead of really waiting, the test scheduler just moves the clock forward, so delays complete immediately and tests stay fast and deterministic.

open as a page

A ViewModel calls viewModelScope.launch { ... } which uses Dispatchers.Main. How do you make this testable with Dispatchers.setMain / Dispatchers.resetMain, and why is each step needed?

level: middleimportance: must knowfreq 75%
basics
~10 s

In a unit test there's no Android main thread, so Dispatchers.Main throws. Before the test, call Dispatchers.setMain(a test dispatcher); after, call Dispatchers.resetMain() to clean up so other tests aren't affected.

open as a page

How do you collect all emissions from a finite Flow in a test and assert the exact sequence of values?

level: juniorimportance: must knowfreq 78%
basics
~10 s

Call toList() on the flow inside a runTest block to gather every emitted value into a List, then compare it to the expected list with assertEquals(expected, actual).

open as a page

When testing a StateFlow in a runTest block, how can you assert its current value without collecting it, and why does that work for StateFlow but not a plain Flow?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Read stateFlow.value. StateFlow always holds a current value you can read directly, so you just assert on it. A plain Flow has no stored value, so there is nothing to read.

open as a page

What is Turbine and why use flow.test { } instead of toList() when testing a Kotlin Flow?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Turbine is a small testing library for Kotlin Flows. Its flow.test { } block lets you wait for and check each emitted value one at a time, instead of collecting everything into a list first.

open as a page

What is the difference between flow.first(), flow.take(n).toList(), and flow.toList() when collecting in a test, and when do you use each?

level: middleimportance: must knowfreq 70%
basics
~20 s

first() grabs only the very first emission and stops. take(n).toList() collects the first n emissions then stops. toList() collects everything until the flow finishes. Use the bounded ones when the flow may not complete on its own.

open as a page

Why should you launch the collection of a hot StateFlow/SharedFlow in runTest's backgroundScope rather than the test scope itself?

level: middleimportance: must knowfreq 70%
basics
~10 s

A hot flow never completes, so collecting it in the main test coroutine would hang forever. backgroundScope runs the collector alongside the test and is automatically cancelled when the test body finishes.

open as a page