A mock created with MockK's `mockk<T>(relaxed = true)` answers calls you never stubbed. What value does it actually return, per return type?
answer
- primitives → 0/false, String → "", Unit → ok
- other types → new RELAXED child mock, never null
- non-null Kotlin types stay satisfied
- defaults are plausible → failures land late
- explicit every always beats the relaxed default
basics
~20 sMockK generates a per-type default: Unit for Unit, 0 for numeric types, false for Boolean, an empty String, empty arrays — and for any other type, including your own domain classes, a brand-new mock of that type which is itself relaxed.
solid answer
~50 sRelaxed mode installs a default answer generator. For types MockK knows how to zero out, it returns the zero value: `Unit`, `0` / `0L` / `0.0` for numbers, `false` for `Boolean`, `""` for `String`, empty arrays; a few well-known JDK types also have built-in empty values. For **everything else — your `User`, your `Order`, an interface, another service — it returns a new mock of that return type, created relaxed as well**. That is the part people miss: a relaxed mock does not hand back `null`, it hands back another relaxed mock. Which is exactly why chains keep working with no stubbing at all. The consequence worth stating in an interview: a relaxed default is *plausible but meaningless*. `0` from a `count()` you forgot to stub is indistinguishable from a real zero, and a child mock flowing into an assertion produces a failure message about a mock rather than about your domain object.
code
kotlin · 15 linesinterface Stats {
fun count(): Int
fun label(): String
fun record(event: String) // Unit
fun nested(): Stats
}
val stats = mockk<Stats>(relaxed = true)
assertEquals(0, stats.count()) // numeric zero
assertEquals("", stats.label()) // empty string
stats.record("x") // no error, returns Unit
val child = stats.nested() // a NEW mock of Stats, itself relaxed
assertEquals(0, child.count()) // so the chain keeps answeringgo deeper
Be able to list the simple defaults — Unit works, numbers are 0, Boolean is false, String is empty — and know that other types come back as another mock rather than null.
Give the two-bucket rule from the erased return type, explain that child mocks are themselves relaxed (which is why chains work), and note explicit stubs take precedence.
Emphasise the diagnostic cost: plausible defaults turn a forgotten stub into a late, misleading failure; flipping the mock to strict is the fastest way to surface the missing stub.
Frame it as a suite-wide signal-quality decision — relaxed mode trades immediate, precise failures for setup brevity, and that trade should be deliberate rather than a default habit.
## What relaxed mode installs A plain MockK mock is *strict*: any call with no matching stub throws a `MockKException` whose message begins `no answer found for:`. `mockk<T>(relaxed = true)` replaces that failure with a **default-answer generator** that has to produce some value for whatever type the called member declares. ## The value table MockK's value generator works from the **erased runtime return type**: | Declared return type | Relaxed default | |---|---| | `Unit` (and Java `void`) | returns normally, no error | | `Int`, `Long`, `Short`, `Byte`, `Double`, `Float` | `0` of that type | | `Boolean` | `false` | | `Char` | the zero char | | `String` | `""` (empty string) | | arrays | an empty array | | any other type — domain classes, interfaces, other services | **a new mock of that type, itself relaxed** | A handful of well-known JDK types have built-in empty values too, but the rule you should carry into an interview is the two-bucket one: *zero-ish value for the simple built-ins, child mock for everything else*. ## Why "a child mock, not null" matters People coming from other ecosystems expect `null` for reference types. MockK does not do that, and the difference is behavioural: - Kotlin's non-null types stay satisfied — a relaxed mock of a function returning `User` cannot return `null` without breaking the type system, so returning a mock `User` is the only choice that keeps the call site sane; - **chains keep working**: `service.repo().findAll()` needs no stubbing at all, because `repo()` produced a relaxed mock that answers `findAll()` in turn; - but the object flowing through your production code is a **mock**, so `toString()` renders as a mock, `equals` is identity-based, and any assertion on it fails with a message about a mock instead of about a domain value. ## The failure mode Relaxed defaults are *plausible*. That is their danger: - `0` returned from an unstubbed `count()` looks exactly like a legitimately empty result. A test asserting "we do nothing when count is zero" passes for the wrong reason. - `""` from an unstubbed `name()` flows into string building and produces output that looks merely odd, not broken. - A child mock passed into an assertion produces `expected: User(id=1) but was: User(#7)` — confusing until you realise nothing was ever stubbed. Strict mocks convert all of these into an immediate, precise error naming the exact unstubbed call. That is the real trade relaxed mode makes: fewer lines of setup, later and vaguer failures. ## Practical technique - **Stub what the assertion depends on, even on a relaxed mock.** Relaxed is for the noise (fire-and-forget metrics, loggers, `Unit` lifecycle calls); values your test reasons about should be explicit stubs. Explicit `every` always wins over the relaxed default. - **When a relaxed default surprises you, flip the mock to strict temporarily.** The resulting `no answer found for:` message names the call you assumed was stubbed. It is the fastest diagnostic there is for this class of bug. - **Watch what escapes the test.** A child mock returned by a relaxed mock can travel far — into a collection, a cache, a saved entity — and only reveal itself several assertions later. ## Interview framing A complete answer is: *zero values for primitives, `""` for String, `Unit` for Unit, empty arrays; a new relaxed mock for every other type — not `null`. That makes deep chains work without stubbing, and it makes forgotten stubs fail late and unclearly instead of immediately.* Candidates who say "it returns null for objects" have not actually watched relaxed mode work.
- Does a relaxed mock ever return `null` for a reference return type?For ordinary non-null reference return types it does not — it produces a new relaxed mock of that type, which is what keeps deep chains working and keeps Kotlin's non-null types satisfied. If you need `null` for a specific call, stub it explicitly with `every { … } returns null`; an explicit stub always takes precedence over the relaxed default.
- An explicit `every` stub and the relaxed default both could apply to a call. Which wins?The explicit stub. Relaxed mode only supplies an answer when no registered stub matches the call, so `every { repo.find(1) } returns account` is used for `find(1)` while `find(2)` still falls back to the generated default. That asymmetry is exactly what makes a mismatched argument matcher silently produce a default instead of an error.
A relaxed mock is a bureaucrat who never says "I don't know": ask for a number and you get zero, ask for a person and you get another bureaucrat with the same policy. Nothing ever refuses you, so you only find out at the end that no real answer was ever given.
saying these in an interview costs you the question
- "A relaxed mock returns null for object types" — it returns a new relaxed mock of that type.
- "Relaxed only affects Unit-returning functions" — that is the narrower `relaxUnitFun` option; `relaxed = true` covers every return type.
- "The relaxed default overrides my every stub" — explicit stubs always win; the default is the fallback.
- "Getting 0 back proves the collaborator returned an empty result" — an unstubbed call returns 0 too, which is the whole diagnostic problem.
- "Relaxed mocks throw if the return type is unknown" — they generate a child mock instead; that silence is the trade.