On a relaxed mock, you call a suspend function you never stubbed. What does it return, and how does this interact with coEvery and coVerify?
answer
- relaxed returns defaults for suspend too
- 0/false/empty/nested relaxed mock
- coEvery overrides the default
- coVerify unaffected by relaxation
- relaxUnitFun for suspend Unit sinks
basics
~20 sA relaxed mock auto-returns a sensible default (0, false, empty, or a child relaxed mock) even for suspend functions, so unstubbed coEvery isn't required. You can still coEvery to override and coVerify to check calls.
solid answer
~40 sMockK's relaxed mocks (mockk<T>(relaxed = true) or @MockK(relaxed = true)) supply default return values for any unstubbed call, including suspend ones — the same defaulting MockK applies to non-suspend functions: primitives get 0/false, String empty, collections empty, other types a nested relaxed mock, and Unit/void just records. This works for coroutine functions because MockK intercepts the call regardless of the suspend modifier. You override specific suspend behavior with coEvery { } returns/coAnswers, and you still verify with coVerify. relaxUnitFun = true relaxes only Unit-returning functions, which is handy for suspend fun emit()-style sinks while keeping value-returning suspend functions strict so a missing coEvery surfaces as a clear failure rather than a silent default that propagates a bogus value.
code
kotlin · 7 linesval repo = mockk<Repo>(relaxUnitFun = true)
runTest {
repo.save(X()) // Unit suspend fun: no stub needed
coEvery { repo.count() } returns 7 // value fns still strict
assertEquals(7, repo.count())
coVerify { repo.save(any()) }
}go deeper
Knows relaxed mocks auto-return defaults so you don't have to stub every suspend call.
Lists the default values and shows coEvery overriding them while coVerify still works.
Chooses relaxUnitFun for suspend Unit sinks to keep value-returning suspend functions strict and avoid silent defaults.
Sets a team policy on strict vs relaxed for coroutine collaborators, weighing test brittleness against the risk of defaults masking missing stubs.
## Relaxed mocks and suspend A **strict** mock (`mockk<T>()`) throws `MockKException` if you call an unstubbed function. A **relaxed** mock (`mockk<T>(relaxed = true)`) instead returns a **default** for any unstubbed call. MockK's interception happens at the same layer for suspend and non-suspend functions, so relaxed defaulting covers suspend functions too. Default values MockK returns: - Numbers ⇒ `0` / `0.0`; `Boolean` ⇒ `false`; `Char` ⇒ `'\u0000'`. - `String` ⇒ `""`; collections (`List`, `Set`, `Map`) ⇒ empty. - `Unit` ⇒ just records the call. - Any other type ⇒ a **nested relaxed mock**. - Nullable return ⇒ MockK typically returns a non-null default of the declared type, not null. ```kotlin interface Repo { suspend fun count(): Int suspend fun names(): List<String> suspend fun save(x: X) // Unit } val repo = mockk<Repo>(relaxed = true) runTest { repo.count() // 0, no stub needed repo.names() // emptyList() repo.save(X()) // recorded, returns Unit coEvery { repo.count() } returns 7 // override when it matters assertEquals(7, repo.count()) coVerify { repo.save(any()) } // still verifiable } ``` ## relaxed vs relaxUnitFun - `relaxed = true` — relax **all** return types. - `relaxUnitFun = true` — relax **only** `Unit`-returning functions; value-returning ones stay strict. For suspend code, `relaxUnitFun = true` is often the sweet spot: fire-and-forget suspend sinks (`suspend fun emit(e)`, `suspend fun log(...)`) need no stub, but a value-returning `suspend fun load(): User` still **fails loudly** if you forget `coEvery`, preventing a silent empty/zero default from masking a missing stub. ## Interaction with coEvery / coVerify - `coEvery` **overrides** the relaxed default for specific calls; precedence goes to the explicit stub. - `coVerify` works the same on relaxed mocks — relaxation affects return values, **not** call recording. - A nested relaxed mock returned for a non-primitive type is itself relaxed, so chained suspend calls keep returning defaults. ## Trade-off / red flag Full `relaxed = true` is convenient but can hide bugs: a forgotten `coEvery` silently yields `0`/empty and the test passes for the wrong reason. Prefer strict mocks plus explicit `coEvery`, or `relaxUnitFun = true`, when the return value is load-bearing.
- Does a relaxed mock returning defaults stop you from verifying the call happened?No. Relaxation only affects return values; coVerify still records and checks invocations normally.
- Why might you prefer relaxUnitFun over full relaxed for coroutine collaborators?Suspend Unit sinks (emit/log/save) need no stub, but value-returning suspend functions stay strict, so a forgotten coEvery fails loudly instead of silently returning 0/empty.
saying these in an interview costs you the question
- Claiming relaxed defaults do not apply to suspend functions
- Believing relaxation suppresses coVerify recording
- Defaulting to relaxed = true everywhere and masking missing stubs
- Thinking nullable returns yield null (MockK returns a non-null default)