Show how to use the value returned by assertFailsWith to assert on an exception's message and its cause.
answer
- val ex = assertFailsWith<T> { ... }
- ex.message is String? — guard nullability
- ex.cause is Throwable? — set when wrapping
- assertIs<T> smart-casts the cause
- assertContains for substring matches
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.
solid answer
~30 s`assertFailsWith` returns the caught exception, so bind it: `val ex = assertFailsWith<ServiceException> { svc.call() }`. Assert the message with `assertEquals("...", ex.message)` or, for partial matches, `assertContains(ex.message!!, "token")` / `assertTrue(ex.message!!.contains(...))`. To verify the wrapped root cause — set when you rethrow with `throw Wrapper("msg", original)` — read `ex.cause`. Pin its type and message: `val cause = ex.cause; assertIs<IOException>(cause); assertEquals("disk full", cause.message)`. `assertIs<T>` smart-casts so subsequent property access is type-safe. Because `message` is nullable (`String?`), use `assertNotNull(ex.message)` or `!!` deliberately. This captures both the surface contract (message text) and the causal chain (cause), which matters when code wraps low-level exceptions into domain ones.
code
kotlin · 17 linesimport kotlin.test.*
class WrapTest {
class DomainError(msg: String, cause: Throwable) : Exception(msg, cause)
private fun run(): Nothing =
throw DomainError("operation failed", IllegalStateException("bad state"))
@Test
fun checksMessageAndCause() {
val ex = assertFailsWith<DomainError> { run() }
assertEquals("operation failed", ex.message)
val cause = ex.cause
assertIs<IllegalStateException>(cause)
assertEquals("bad state", cause.message)
}
}go deeper
Can capture the exception and check its message with assertEquals.
Handles message nullability and asserts the cause's type and message, using assertIs for smart-casting.
Tests the full causal chain to lock down exception-wrapping behavior and explains why assertIs beats assertTrue+is.
Defines conventions for asserting failure contracts (message + cause) and avoids brittle exact-string coupling where appropriate.
## Capture, then assert `assertFailsWith<T>` returns the exception, enabling fluent follow-up assertions instead of try/catch. ```kotlin import kotlin.test.* class LoaderTest { @Test fun wrapsLowLevelFailure() { val ex = assertFailsWith<ConfigException> { loadConfig("missing.yml") } // 1) message of the top exception assertEquals("failed to load config", ex.message) // 2) the cause (the wrapped original) val cause = ex.cause assertIs<java.io.FileNotFoundException>(cause) // smart-casts cause assertContains(cause.message ?: "", "missing.yml") } } ``` ## Key APIs - **`ex.message`** — type `String?` (nullable). Guard with `assertNotNull(...)`, `!!`, or `?: ""` before string ops. - **`ex.cause`** — type `Throwable?`. Populated when code does `throw Domain("...", original)` or `initCause(...)`. - **`assertEquals` / `assertContains`** — exact vs substring checks on the message. - **`assertIs<T>(value)`** — asserts `value is T` AND **smart-casts** it, so after the call `cause` is typed as `T` and you can read its members without a manual cast. This is preferable to `assertTrue(cause is T)`, which does not smart-cast a `val` of platform/nullable origin as cleanly. ## Where `cause` comes from The `cause` is part of `Throwable`. Wrapping code sets it via the secondary constructor: ```kotlin try { rawLoad(path) } catch (e: IOException) { throw ConfigException("failed to load config", e) } ``` Here `e` becomes `cause`. Asserting on it verifies the wrapping behavior, not just the surface message. ## Common pitfalls - Forgetting `message` is nullable and calling `.contains` on `String?` (won't compile without `!!`/`?:`). - Asserting the wrapper's message but never checking `cause`, so wrapping regressions slip through. - Using `assertTrue(ex is X)` and then needing a redundant cast — prefer `assertIs<X>`. ## Summary Bind the returned exception → assert `message` (handle nullability) → assert `cause` type and message with `assertIs` to capture the full causal contract.
- Why prefer assertIs<T>(cause) over assertTrue(cause is T)?assertIs<T> both asserts the type and smart-casts the value to T, so afterward you can access T-specific members without a manual cast; it also produces a clearer failure message.
- Why must you handle ex.message carefully?message is typed String?, so calling String operations directly won't compile; you need assertNotNull, !!, or an elvis fallback first.
saying these in an interview costs you the question
- Calling .contains directly on ex.message without handling null
- Never asserting cause, so wrapping regressions go unnoticed
- Using try/catch to grab the exception when the return value already provides it
- Assuming cause is always non-null