skip to content

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?

level: seniorimportance: should knowfreq 40%

answer

  1. relaxed returns defaults for suspend too
  2. 0/false/empty/nested relaxed mock
  3. coEvery overrides the default
  4. coVerify unaffected by relaxation
  5. relaxUnitFun for suspend Unit sinks

basics

~20 s

A 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 s

MockK'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 lines
kotlin
val 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

for a junior

Knows relaxed mocks auto-return defaults so you don't have to stub every suspend call.

for a middle

Lists the default values and shows coEvery overriding them while coVerify still works.

for a senior

Chooses relaxUnitFun for suspend Unit sinks to keep value-returning suspend functions strict and avoid silent defaults.

for a principal

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)

context