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 pageshowhide
explore
- How Kotlin Does Testing6 questions
- kotlin.test21 questions
- kotlin.test Assertions6 questions
- assertFailsWith5 questions
- Multiplatform Test Annotations5 questions
- JUnit 5 from Kotlin5 questions
- MockK25 questions
- every / verify5 questions
- Relaxed Mocks & Spies5 questions
- Coroutine Mocking (coEvery / coVerify)5 questions
- Object, Static & Constructor Mocking5 questions
- Verification Modes & Ordering5 questions
- Kotest20 questions
- Spec Styles5 questions
- Matchers DSL (shouldBe)5 questions
- Property-Based Testing5 questions
- Data-Driven Tests5 questions
- Coroutine Testing20 questions
- runTest & Virtual Time5 questions
- Standard vs Unconfined TestDispatcher5 questions
- Advancing Virtual Time5 questions
- Injecting Test Dispatchers5 questions
- Flow Testing15 questions
- Turbine for Flow5 questions
- Collecting & Asserting Emissions5 questions
- Testing StateFlow / SharedFlow5 questions
questions
107 · 6 sectionsWhat is the kotlin.test library, and how do you write a basic assertion-based unit test with it?
basics
~10 skotlin.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.
How does MockK let you create and verify mocks in Kotlin, and why is it preferred over Mockito for Kotlin code?
basics
~20 sMockK 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 { ... }.
What does runTest from kotlinx-coroutines-test do, and why is it needed for deterministic coroutine tests?
basics
~10 srunTest 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.
What is Kotest, and what does its spec-style DSL give you over plain kotlin.test?
basics
~10 sKotest 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.
How do you deterministically test a Kotlin Flow, including hot flows like StateFlow that never complete?
basics
~20 sFor 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.
In kotlin.test, how do you assert that a block of code throws a specific exception type, and what does the assertion return?
basics
~20 sUse 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.
What does kotlin.test's assertEquals do, and why does the order of its first two arguments matter?
basics
~20 sassertEquals 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.
How do you write a JUnit 5 test method with a human-readable name in Kotlin, and why is this idiomatic?
basics
~10 sIn 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.
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?
basics
~20 sUse @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.
Show how to use the value returned by assertFailsWith to assert on an exception's message and its cause.
basics
~10 sCapture 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.
How do you create a mock with MockK and make one of its methods return a fixed value?
basics
~10 sCreate 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.
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?
basics
~10 severy 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.
In MockK, what does creating a mock with mockk<T>(relaxed = true) do, and how does it differ from a plain mockk<T>()?
basics
~10 sA 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.
How do you use mockkObject to stub a method on a Kotlin object (singleton) or companion object in a MockK test?
basics
~10 sCall 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.
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?
basics
~20 sUse 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.
What is Kotest's withData and why would you use it instead of writing a separate test for each input?
basics
~10 swithData 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.
What is Kotest's matcher DSL, and how do you assert that a value equals an expected value using shouldBe and shouldNotBe?
basics
~20 sKotest 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.
What is property-based testing in Kotest, and how does forAll differ from a traditional example-based test?
basics
~10 sInstead 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.
In Kotest, how do you choose which test-layout style your test file uses, and name the four common spec styles?
basics
~10 sYou 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.
Explain how Kotest's table(), row(), and forAll work together for table-driven testing. Show a multi-column example.
basics
~10 sYou 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.
Why should a class that launches coroutines take a CoroutineDispatcher as a constructor parameter instead of hardcoding Dispatchers.IO or Dispatchers.Default?
basics
~10 sSo 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.
In kotlinx-coroutines-test, what are StandardTestDispatcher and UnconfinedTestDispatcher, and how do they differ in when they execute a newly launched coroutine?
basics
~10 sBoth 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.
What is runTest and why do you use it to test suspend functions instead of runBlocking?
basics
~10 srunTest 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.
What is virtual time in coroutine tests, and why does a delay(10_000) inside runTest finish almost instantly instead of taking 10 seconds?
basics
~10 sCoroutine 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.
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?
basics
~10 sIn 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.
How do you collect all emissions from a finite Flow in a test and assert the exact sequence of values?
basics
~10 sCall 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).
What is Turbine and why use flow.test { } instead of toList() when testing a Kotlin Flow?
basics
~20 sTurbine 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.
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?
basics
~20 sfirst() 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.