skip to content

How do you assert that a block of code throws a specific exception in Kotest, and what is the difference between shouldThrow<T> and shouldThrowExactly<T>?

level: middleimportance: must knowfreq 65%

answer

  1. shouldThrow<T> { } returns the exception
  2. shouldThrow = T or subclass
  3. shouldThrowExactly = exact runtime class
  4. shouldThrowAny / shouldNotThrow
  5. reified generic enables runtime type check

basics

~10 s

Wrap the risky code in shouldThrow<SomeException> { ... }. The test passes only if that code throws that exception type. It also hands you the caught exception so you can check its message.

solid answer

~40 s

Use `shouldThrow<T> { block }`: the lambda must throw a `T` (or subclass) or the assertion fails. It returns the caught exception so you can assert further: `val ex = shouldThrow<IllegalArgumentException> { parse("x") }; ex.message shouldContain "invalid"`. The key distinction: `shouldThrow<T>` accepts `T` or any subclass, while `shouldThrowExactly<T>` requires the runtime class to be exactly `T` (subclasses fail). There's also `shouldThrowAny { }` (any Throwable) and `shouldThrowUnit<T> { }` for blocks whose last expression isn't the throwing call. If the block throws a different type, Kotest surfaces it rather than swallowing it. These are reified generic functions, so the type is available at runtime to perform the `is`/exact-class check.

code

kotlin · 13 lines
kotlin
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.throwables.shouldThrowExactly
import io.kotest.matchers.shouldBe

open class AppError(msg: String) : RuntimeException(msg)
class NotFound(msg: String) : AppError(msg)

// subtype accepted
val e = shouldThrow<AppError> { throw NotFound("missing") }
e.message shouldBe "missing"

// exact required -> subclass would fail
shouldThrowExactly<NotFound> { throw NotFound("missing") }

go deeper

for a junior

Can wrap code in shouldThrow<T> { } and verify an exception is thrown.

for a middle

Distinguishes shouldThrow (subtype) from shouldThrowExactly (exact), and uses the returned exception to assert the message.

for a senior

Explains the reified-generic mechanism, the full family (Any/Not/Unit variants), and exception testing in suspend/coroutine code.

for a principal

Sets conventions on testing error contracts precisely (exact vs subtype) so refactors don't silently broaden caught types.

## Asserting exceptions Kotest provides exception matchers as **reified inline functions**. `reified` means the generic type parameter `T` is available at runtime, so Kotest can check `caught is T` and read `T::class`. ```kotlin import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.matchers.string.shouldContain val ex = shouldThrow<IllegalArgumentException> { require(false) { "value must be positive" } } ex.message shouldContain "positive" // returned exception is usable ``` ## The variants - **`shouldThrow<T> { }`** — passes if the block throws `T` **or any subclass** of `T`. Most common. - **`shouldThrowExactly<T> { }`** — passes only if the thrown object's runtime class is **exactly** `T`. A subclass fails. Use when the precise type matters. - **`shouldThrowAny { }`** — passes if the block throws **any** `Throwable`; returns it. - **`shouldNotThrow<T> { }`** / **`shouldNotThrowAny { }`** — assert that no (such) exception is thrown. - **`shouldThrowUnit<T> { }`** — variant for blocks whose final expression returns `Unit` (avoids the lambda being treated as a value-returning expression in some call sites). ## Subtype vs exact Given `class MyError : IllegalStateException()`: ```kotlin shouldThrow<IllegalStateException> { throw MyError() } // passes (subclass) shouldThrowExactly<IllegalStateException> { throw MyError() } // FAILS: runtime class is MyError shouldThrowExactly<MyError> { throw MyError() } // passes ``` ## Behavior on the wrong exception If the block throws something that is not assignable to `T`, the matcher does not silently pass — it fails the assertion, and the unexpected exception is reported so you can see what actually happened. If the block throws nothing, the assertion also fails with a 'no exception was thrown' message. ## Suspend functions The blocks are ordinary lambdas; for `suspend` code you call them inside a coroutine test (`runTest` / `runBlocking`), and the matcher still works because the throw propagates normally. ## Why it returns the exception Returning the caught exception lets you chain further matchers on its `message`, `cause`, or custom fields — keeping the exception assertion and its detail checks in one place.

  • When would you prefer shouldThrowExactly over shouldThrow?
    When you must guarantee the precise exception type and not accept a more specific subclass — e.g. asserting a generic IllegalStateException is thrown, not a custom subtype.
  • What happens if the block throws a different exception type than T?
    The assertion fails and Kotest reports the unexpected exception; it is not swallowed or treated as a pass.
  • How do you test exceptions from a suspend function?
    Call it inside runTest/runBlocking; the throw propagates so shouldThrow works normally.

saying these in an interview costs you the question

  • Believing shouldThrow<T> only matches exactly T (it matches subclasses too)
  • Ignoring the returned exception and adding a separate try/catch to inspect the message
  • Thinking a wrong-type exception makes the assertion pass
  • Putting assertions after the throwing line outside the block, where they never run

context