skip to content

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