skip to content

kotlin.test

kotlin.test is the assertion API that works in common code across every target, mapping to the platform's real test framework underneath. On the JVM it usually sits on top of JUnit, which is where the two topics meet.

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

explore

questions

21

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%

answer

  1. kotlin.test.assertFailsWith<T> { block }
  2. Returns the caught exception (typed T)
  3. Subtypes pass too
  4. Non-reified overload takes KClass
  5. Fails if nothing thrown or wrong type

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.

solid answer

~30 s

Call assertFailsWith<SomeException> { ... } from kotlin.test. The lambda is the code under test; the assertion passes only if it throws SomeException (or a subtype). It returns the caught exception instance, so you typically write `val ex = assertFailsWith<IllegalArgumentException> { parse(bad) }` and then assert on `ex.message`. If no exception is thrown, or a different type is thrown, the assertion fails with a clear message. There is also a non-reified overload `assertFailsWith(SomeException::class) { ... }`. This is the idiomatic, multiplatform way to test failures; it avoids the try/catch/fail pattern and works on JVM, JS, and Native.

code

kotlin · 16 lines
kotlin
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertEquals

class AgeTest {
    private fun setAge(a: Int): Int {
        require(a >= 0) { "age must be non-negative" }
        return a
    }

    @Test
    fun negativeAgeRejected() {
        val ex = assertFailsWith<IllegalArgumentException> { setAge(-1) }
        assertEquals("age must be non-negative", ex.message)
    }
}

go deeper

for a junior

Knows to call assertFailsWith<T> { ... } and that the test passes only when T is thrown.

for a middle

Captures the return value to assert on message, and knows subtypes match and there is a KClass overload.

for a senior

Frames it as the multiplatform-safe alternative to try/catch/fail and JUnit assertThrows, with consistent semantics across targets.

for a principal

Can articulate API design trade-offs (reified vs KClass, returning T for fluent follow-up) and guide team conventions for failure-path testing.

## What `assertFailsWith` does `assertFailsWith` is an assertion from the **`kotlin.test`** library (import `kotlin.test.assertFailsWith`). It verifies that executing a block of code throws an exception of an expected type. If the block does **not** throw, or throws a **different** type, the assertion fails. ## The reified generic form The common form uses a **reified type parameter** — `reified` means the generic type `T` is available at runtime, so you can write the exception type in angle brackets instead of passing a `Class`/`KClass`: ```kotlin import kotlin.test.assertFailsWith import kotlin.test.Test class ParserTest { @Test fun rejectsEmptyInput() { assertFailsWith<IllegalArgumentException> { require(false) { "empty" } } } } ``` ## It RETURNS the exception A key detail: `assertFailsWith` **returns the caught exception**, typed as `T`. Capture it to make further assertions: ```kotlin val ex = assertFailsWith<IllegalArgumentException> { parse("") } assertEquals("empty", ex.message) ``` The return type is exactly `T`, so `ex.message` and any type-specific properties are accessible without a cast. ## Subtypes count The match is **assignment-compatible**: throwing a subtype of `T` passes. `assertFailsWith<RuntimeException> { throw IllegalStateException() }` succeeds because `IllegalStateException` is a `RuntimeException`. ## Non-reified overload When you only have a `KClass` (e.g. computed at runtime), use `assertFailsWith(exceptionClass: KClass<T>, block)`: ```kotlin assertFailsWith(IllegalStateException::class) { check(false) } ``` ## Summary - Import from `kotlin.test`, not JUnit. - Reified form: `assertFailsWith<T> { ... }`. - Passes on `T` or any subtype; fails on no-throw or wrong type. - Returns the exception `T` for follow-up assertions.

  • What happens if the block does not throw anything?
    The assertion fails with a message saying an exception of the expected type was expected but the block completed normally.
  • Does it pass if a subclass of the expected exception is thrown?
    Yes. The match is assignment-compatible, so any subtype of the declared type satisfies it.

Like setting a specific mousetrap: it only counts as a catch if the right animal (exception type) springs it, and you get to inspect what you caught.

saying these in an interview costs you the question

  • Thinking it comes from JUnit's Assertions class instead of kotlin.test
  • Not knowing it returns the exception, so wrapping it in try/catch to grab the message
  • Believing only the exact type passes, not subtypes
  • Confusing it with assertThrows/expected= annotations

context

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

What is special about assertNotNull's return value compared with assertTrue(x != null)?

level: middleimportance: must knowfreq 65%

basics

~20 s

assertNotNull both checks the value isn't null and gives you back the value as a non-null type. After it, you can use the value without ?. or !!. assertTrue(x != null) only checks; it doesn't unwrap the type.

open as a page

Why does @BeforeAll often fail in a Kotlin JUnit 5 test, and how does @TestInstance(PER_CLASS) fix it?

level: middleimportance: must knowfreq 65%

basics

~10 s

By default JUnit needs @BeforeAll to be static, but Kotlin classes don't have plain static methods. Adding @TestInstance(PER_CLASS) lets JUnit reuse one instance so a normal (non-static) @BeforeAll works.

open as a page

What is the difference between assertFails and assertFailsWith in kotlin.test, and when would you choose each?

level: middleimportance: should knowfreq 55%

basics

~10 s

assertFails only checks that something was thrown, ignoring the type, and returns the Throwable. assertFailsWith checks for a specific type. Use assertFails when any failure is fine; use assertFailsWith when the exact exception matters.

open as a page

Explain the difference between assertSame and assertEquals, and give a case where each is correct.

level: middleimportance: should knowfreq 55%

basics

~10 s

assertEquals checks two values are equal by content (==). assertSame checks they are the exact same object in memory (===). Use assertEquals for value comparison, assertSame to confirm something returned the very same instance.

open as a page

When should you use assertTrue/assertFalse over assertEquals, and what does the lambda-message overload give you?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use assertTrue/assertFalse for genuine yes/no conditions like isEmpty() or contains(). For comparing two values use assertEquals because it shows both values on failure. assertTrue also has a form where the message is built only if the test fails.

open as a page

How do you use @Nested in Kotlin to group JUnit 5 tests, and what lifecycle behaviour applies to nested classes?

level: middleimportance: should knowfreq 50%

basics

~10 s

You put related tests inside an inner class marked @Nested. JUnit runs them as a sub-group, and the inner class can access the outer class's fields, which helps share setup.

open as a page

What Gradle/source-set setup is required to write kotlin.test tests in common code for a multiplatform module, and how do you pick the JVM test runner?

level: middleimportance: should knowfreq 30%

basics

~20 s

Add the kotlin-test dependency to the commonTest source set and put your tests there using kotlin.test imports. For the JVM you pick JUnit 4 or 5 either by a small dependency or by calling useJUnitPlatform() on the test task.

open as a page

Map kotlin.test's @BeforeTest, @AfterTest, and @Ignore to their JUnit 5 equivalents, and explain how @Ignore behaves at the class level versus the function level.

level: middleimportance: should knowfreq 40%

basics

~10 s

@BeforeTest is like JUnit 5's @BeforeEach, @AfterTest is like @AfterEach, and @Ignore is like @Disabled. @Ignore on a class skips every test in it; on one function it skips just that test.

open as a page

How do you assert that a suspend function throws a specific exception, and what subtle issues arise with assertFailsWith and coroutines?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Run the suspend call inside runTest (or runBlocking) and put it in the assertFailsWith lambda. Be careful: the lambda is not a suspend lambda by default, so call the suspend function from within a coroutine scope.

open as a page

What does fail() return, and how does that Nothing return type let you use it in expressions?

level: seniorimportance: should knowfreq 45%

basics

~20 s

fail() always makes the test fail immediately. It returns Nothing, the type with no values, so the compiler treats anything after it as unreachable and lets you use it anywhere a value is expected, like the right side of ?:.

open as a page

Compare the two ways to run a once-per-class @BeforeAll in a Kotlin JUnit 5 test, and explain when each is appropriate.

level: seniorimportance: should knowfreq 40%

basics

~10 s

You can either put @BeforeAll in a companion object with @JvmStatic, making it truly static, or add @TestInstance(PER_CLASS) so a normal method works. Static suits truly shared resources; PER_CLASS suits instance-bound setup.

open as a page

How does kotlin.test make @Test in common code resolve to the right test framework on each target? Explain the expect/actual (typealias) mechanism.

level: seniorimportance: should knowfreq 35%

basics

~20 s

kotlin.test declares the annotations once in common code. For each platform it provides a matching real version, so on the JVM @Test becomes JUnit's @Test, on JS it becomes a JS runner's, and so on. One annotation, many backings.

open as a page

Across the kotlin.test multiplatform API, how does assertFailsWith stay portable, and how does it compare to JUnit's assertThrows when running on the JVM?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

assertFailsWith is part of kotlin.test, so the same test code runs on JVM, JS, and Native. JUnit's assertThrows is JVM-only. Both check a thrown type and return the exception, but only kotlin.test is multiplatform.

open as a page

Why use the kotlin.test assertion family instead of a framework's native asserts, and what do assertEquals vs assertNotEquals cover?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

kotlin.test gives one set of assertions that work the same on every Kotlin platform and produce clear messages. assertEquals checks values match; assertNotEquals checks they differ. On the JVM, failures still flow through your test framework.

open as a page

A Kotlin test class uses @TestInstance(PER_CLASS) and passes locally but fails intermittently in CI. What are the likely causes and how do you diagnose them?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

PER_CLASS reuses one instance, so leftover state from one test can affect another. If tests run in a different order in CI, hidden dependencies surface. Reset shared state and pin or remove order assumptions.

open as a page

A team wants their shared kotlin.test suite to cover suspend functions and per-class setup uniformly across JVM, JS, and Native. What limits of the multiplatform test annotations do you flag, and how do you handle async tests?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

kotlin.test's annotations have no common way to run code once per class and no built-in support for suspend tests. For coroutines, use runTest from the coroutines-test library inside a @Test. Anything JVM-only goes in a JVM-only test source set.

open as a page