skip to content

Verification

Asserting that interactions happened — how many times, in what order, and nothing else besides. Interviewers probe verification depth because over- and under-verifying are both common test-quality failures.

on this pageshow

explore

questions

16

In MockK, what does a bare `verify { mailer.send(any()) }` with no count parameter actually assert, and how do you assert that the call happened exactly once?

level: middleimportance: must knowfreq 50%

answer

  1. defaults: atLeast = 1, atMost = MAX_VALUE, exactly = -1
  2. bare verify = at least once, not once
  3. exactly = n fixes both bounds
  4. count is over calls matching your matchers
  5. stubbing inside every does not count as a call

basics

~20 s

It asserts at least one matching call. MockK's verify defaults are atLeast = 1, atMost = Int.MAX_VALUE and exactly = -1 (unspecified), so two or ten matching calls also pass. For exactly-once, write verify(exactly = 1) { ... }.

solid answer

~50 s

`verify` has default parameters: `ordering`, `inverse = false`, `atLeast = 1`, `atMost = Int.MAX_VALUE`, `exactly = -1`, `timeout = 0`. `exactly = -1` means "not specified", so a bare block means **at least once** — an accidental duplicate, a retry loop, or a double-subscribed listener passes silently. If the count is part of what you are asserting, say it: `verify(exactly = 1) { mailer.send(any()) }`. Setting `exactly` fixes both bounds, so use it *or* `atLeast`/`atMost`, never both in the same call — the intent becomes unreadable. When to use which: `exactly = 1` for effects that must not repeat (sending mail, charging a card, publishing an event); a bare `verify` or `atLeast = 1` when you only care that a collaborator was reached and the exact count is an implementation detail; `atMost` when you are bounding retries. A count you did not think about is a count you did not assert.

code

kotlin · 6 lines
kotlin
mailer.send(msg)
mailer.send(msg)   // accidental duplicate

verify { mailer.send(any()) }              // passes: atLeast = 1 by default
verify(exactly = 1) { mailer.send(any()) } // fails: 2 matching calls
verify(atMost = 3) { mailer.send(any()) }  // passes: bounded, not pinned

go deeper

for a junior

Know that verify without parameters means at least once, and reach for verify(exactly = 1) when repetition would be a bug.

for a middle

Quote the defaults — atLeast = 1, atMost = Int.MAX_VALUE, exactly = -1 — and explain that the count is over calls matching your matchers.

for a senior

Tie the choice to the effect being verified: exact counts for non-idempotent side effects, bounds for retries, no count where the number is incidental.

for a principal

Frame counts as part of the collaborator contract you are freezing, and set a team default so assertions mean what their readers think they mean.

## The parameter defaults are the whole answer MockK's `verify` is a function with default arguments: ```kotlin verify( ordering = Ordering.UNORDERED, inverse = false, atLeast = 1, atMost = Int.MAX_VALUE, exactly = -1, timeout = 0 ) { ... } ``` `exactly = -1` is the sentinel for "unspecified". So an unadorned `verify { mailer.send(any()) }` asserts *at least one* matching call and nothing more. This is the single most common misreading of MockK verification: people write it meaning "this happened" and read it back later as "this happened once". ## Why the difference matters in practice The bugs a count assertion is supposed to catch are precisely the duplicate-call bugs: - a retry wrapper that also retries at a lower layer, so an email goes out twice; - an event listener registered in both a constructor and an init block; - a loop that was meant to batch but sends per item; - a caching layer that stopped caching, doubling calls to a paid API. Every one of those passes a bare `verify`. `verify(exactly = 1) { ... }` catches all of them. ## Choosing a count deliberately - **`exactly = n`** — the count is part of the contract. Non-idempotent effects (payment, mail, publish, delete) belong here, and so does "we call the expensive downstream once per request". - **bare `verify` / `atLeast = 1`** — you care that the collaborator was reached; how many times is an implementation detail you do not want to pin. Legitimate, but be honest that you chose it. - **`atMost = n`** — you are bounding something, typically retries or fan-out, without pinning the exact number. - **`atLeast` with `atMost`** — an explicit range for genuinely non-deterministic call counts. Setting `exactly` fixes both ends of the range, so combining it with `atLeast`/`atMost` in the same call says nothing useful to a reader even where it is accepted; pick one form. Passing a negative `exactly` (other than the -1 sentinel) is rejected outright. ## What a count is actually counting The number is not "how many times this method ran" — it is **how many recorded calls on that mock match this method with these argument matchers**. `verify(exactly = 1) { repo.save(any()) }` fails if `save` ran twice with different arguments, whereas `verify(exactly = 1) { repo.save(user) }` looks only at calls whose argument equals `user`. Widening or narrowing the matchers changes the population being counted, which is the usual explanation for a count that surprises you. Also worth knowing: calls you made while *stubbing* — inside `every { }` — are recording, not invocation, and do not add to the count. Only real invocations by the code under test do. ## Related knobs in the same call `inverse = true` flips the assertion: `verify(inverse = true) { mailer.send(any()) }` asserts the matching call did **not** happen, which is the same practical statement as `exactly = 0`. `ordering` and `timeout` belong to sequencing and asynchronous verification respectively and are separate concerns from counting; mentioning that they exist on the same function is enough here. ## How to say it in an interview The crisp version is three sentences: MockK's `verify` defaults to `atLeast = 1`, so a bare block means *at least once*; if the count matters, write `exactly = n`, and use `atLeast`/`atMost` for a deliberate range; and remember that the count is over calls matching your argument matchers, not over every invocation of the method. Candidates who assume `verify { }` means exactly once tend to have a suite full of assertions that are weaker than they believe.

  • When would you deliberately leave a verification without an exact count?
    When the number of calls is an implementation detail you do not want to freeze. If a repository read happens once or twice depending on a cache, pinning the count makes the test fail on a harmless refactor while telling you nothing about behaviour. Save exact counts for effects where repetition is itself a bug — payments, emails, published events, expensive downstream calls.
  • Does calling a mock while stubbing it inside an every block add to the verified count?
    No. Code inside an every block is executed under MockK's call recorder to describe a stub, not to invoke the mock, so it produces no recorded call. Only real invocations made by the code under test are counted. This is why arranging elaborate stubs never breaks a later exactly = 1 assertion.

saying these in an interview costs you the question

  • Reading a bare verify { } as "called exactly once"
  • Thinking exactly = 1 is the default and atLeast must be opted into
  • Passing exactly together with atLeast/atMost and expecting a meaningful combined constraint
  • Forgetting that the count only covers calls matching the argument matchers you wrote
  • Believing calls made inside every blocks are counted as interactions

context

open as a page

What does MockK's `verify { repo wasNot Called }` assert, and how is that different from `verify(exactly = 0) { repo.save(any()) }`?

level: juniorimportance: should knowfreq 40%

basics

~20 s

wasNot Called asserts the mock received zero recorded calls at all — any method, any arguments. exactly = 0 is narrower: that one method with those matchers never matched, while other calls on the same mock are allowed.

open as a page

MockK's confirmVerified(mock) throws when a mock has recorded calls that nothing verified. Mechanically, which invocations end up in that record, what marks one as verified, and where in a test must the call be placed?

level: middleimportance: should knowfreq 45%

basics

~20 s

Every real invocation on the mock is recorded — stubbed, relaxed-default, property access, suspend calls alike. Any verify block whose matchers match a recorded call marks it verified. confirmVerified verifies nothing itself; it asserts the leftover set is empty, so it goes last.

open as a page

MockK offers verifyAll alongside verifyOrder and verifySequence. What does verifyAll assert that a plain verify does not, which calls fall inside its scope, and how does its failure condition differ from verifySequence's?

level: middleimportance: should knowfreq 35%

basics

~20 s

verifyAll is exhaustive but order-free: every call you list must have happened, and every recorded call on the mocks named in the block must be matched by some entry. verifySequence adds the requirement that the order match exactly.

open as a page

In MockK, what exactly does a verifyOrder block check about the calls a mock recorded — which calls are allowed to appear between the ones you list, how do you assert that the same call happened twice in a row, and what makes the block fail?

level: middleimportance: should knowfreq 45%

basics

~20 s

verifyOrder asks whether the listed calls appear in that relative order somewhere in MockK's chronological call record. Any other calls in between are ignored. A repeat must be listed once per occurrence. It fails only when no in-order match exists.

open as a page

MockK's verify accepts a timeout parameter, as in verify(timeout = 500) { listener.onDone() }. What does MockK actually do during those 500 milliseconds, and how is that different from calling Thread.sleep(500) before a plain verify?

level: middleimportance: should knowfreq 32%

basics

~20 s

MockK re-evaluates the verification block against the mock's recorded calls until it passes or the deadline expires, returning the instant it succeeds. Thread.sleep always burns the full delay and then checks once, at a single fixed moment.

open as a page

A MockK assertion `verify(exactly = 2) { repo.save(any()) }` fails, reporting a different number of matching calls than you expected. How do you work out whether the production code or the assertion is wrong?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Read the failure output first: it names the expected call with its matchers, how many matching calls it found, and the calls actually recorded on the mock. Then check matcher breadth — any() folds distinct arguments into one count — plus overloads, argument equality, and calls arriving from setup or retries.

open as a page

MockK offers excludeRecords { } to keep nominated calls out of a mock's record. Mechanically, what does it change — including calls that already happened before it runs — and what capability do you give up for the excluded calls?

level: seniorimportance: should knowfreq 30%

basics

~20 s

excludeRecords deletes matching calls from the mock's record and stops future matching calls being recorded. By default it also purges ones already recorded; pass current = false to keep those. Excluded calls become invisible to every verification, not just exhaustive ones.

open as a page

A previously green test that uses MockK's verifySequence starts failing after a teammate adds a metrics call to the same collaborator — the assertions themselves were not touched. Explain the mechanism, and how you would restore the test without weakening the guarantee that actually matters.

level: seniorimportance: should knowfreq 40%

basics

~20 s

verifySequence requires the listed calls to match the referenced mocks' recorded calls one-to-one, in order. The new metrics call is an unmatched record, so the block fails. Fix it by moving to verifyOrder for the contractual order, or by narrowing what the mock exposes.

open as a page

You need to assert that a collaborator was called from a background thread or from a coroutine launched on a real dispatcher. How do MockK's verify(timeout = ...) and coVerify(timeout = ...) make that reliable, and what situation makes the verification burn the whole timeout and fail even though the production code is correct?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Calls from any thread are recorded on the same mock, so MockK can poll for them from the test thread; coVerify does the same for suspend calls. It fails wrongly when the pending work can only run on the very thread MockK is blocking, because that work never gets to execute.

open as a page

How does MockK's verify(timeout = ...) combine with count modes such as exactly, atLeast and atMost, and why does adding a timeout to a "never happened" assertion (exactly = 0, wasNot Called, or inverse = true) give you false confidence?

level: seniorimportance: should knowfreq 24%

basics

~20 s

MockK retries while the verification fails and stops at the first success. So atLeast waits for the nth call, but any already-satisfied check — exactly = 0, wasNot Called, inverse, or an atMost that is currently under the bound — passes on the first attempt and never waits at all.

open as a page

In MockK, if the same interaction is verified twice in one test — say `verify(exactly = 1) { repo.save(user) }` runs inside two different helper functions — does the second verification fail because the call was already 'used up'?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

No. MockK verification is non-destructive: each verify block re-scans the mock's recorded calls and counts matches from scratch. Counts are absolute, not remaining, so both assertions see one call and both pass — and counts never add up across blocks.

open as a page

Besides checking that calls were verified, MockK can flag stubs that were never used, via checkUnnecessaryStub. What condition makes it throw, what class of bug does it catch that confirmVerified structurally cannot, and when does it produce false alarms?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

checkUnnecessaryStub(mock) throws if the mock has stubs that no actual call ever matched. It inspects the answer table, so it catches dead or mis-matched stubs — the opposite blind spot from confirmVerified, which only inspects recorded calls. Shared setup stubbing more than a test needs causes false alarms.

open as a page

Your team is deciding whether to turn MockK's exhaustive checks — confirmVerified on mocks and unnecessary-stub detection — on by default across a large Kotlin test suite. How do you reason about where each pays for itself, and what does heavy use of relaxed mocks do to that calculation?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Treat them separately. Dead-stub detection is cheap and its failures are nearly always real, so enable it broadly. confirmVerified freezes the whole interaction with a mock, so scope it to narrow ports where an unexpected call is itself a defect. Relaxed mocks generate incidental records that make blanket confirmVerified noisy.

open as a page

You are setting the house style for a Kotlin service whose handlers orchestrate several collaborators — some orderings are genuinely contractual, most are incidental. How do you decide, per test, between MockK's plain verify, verifyOrder, verifyAll and verifySequence, and how does the scoping of an exhaustive block affect that decision?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Decide on two axes: is completeness part of the contract, and is order part of the contract. verify asserts neither, verifyOrder order only, verifyAll completeness only, verifySequence both. Exhaustive blocks bind only the mocks they name, so scope them narrowly.

open as a page

In an async-heavy test suite, when would you rely on MockK's verify(timeout = ...) versus building deterministic synchronization — for example counting down a latch inside an answers block, or injecting the executor or dispatcher so the work runs on the test thread? How do you keep such a suite from becoming flaky or slow?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Prefer determinism where you own the seam: inject the executor or dispatcher so the work is synchronous, or count a latch down inside an answers block. Use timeout verification at edges you cannot control. Never sleep, and set timeouts generously since only failures pay.

open as a page