skip to content

Explain verify(atLeast = n), verify(atMost = n), and how to assert a call count falls within a range in MockK. What happens if a verified call occurs more times than atMost allows?

level: middleimportance: should knowfreq 55%

answer

  1. atLeast = lower bound, atMost = upper bound
  2. both together = inclusive range
  3. atMost includes zero calls
  4. exactly = atLeast n AND atMost n
  5. ranges avoid brittle exact counts

basics

~20 s

atLeast = n requires n or more calls; atMost = n allows up to n. Combine them in one verify to assert a range. If calls exceed atMost, verification fails because too many matching invocations were recorded.

solid answer

~40 s

verify(atLeast = n) { mock.foo() } passes when foo was called n or more times; verify(atMost = n) { mock.foo() } passes when it was called n or fewer times (including zero). You can pass both — verify(atLeast = 1, atMost = 3) { mock.foo() } — to assert the count is within an inclusive range. Exceeding atMost fails the test with a message reporting how many matching calls were found versus the allowed maximum. These named parameters cannot be combined with exactly (exactly is shorthand for atLeast = n, atMost = n). atMost is useful for retry/throttle logic where you want "no more than N attempts" without pinning an exact number, keeping the test resilient to incidental extra-but-bounded calls.

code

kotlin · 5 lines
kotlin
// retry should fire between 1 and 3 times
verify(atLeast = 1, atMost = 3) { client.send(req) }

// 'cleanup at most once, possibly zero'
verify(atMost = 1) { resource.close() }

go deeper

for a junior

Knows atLeast and atMost set lower/upper bounds on call counts.

for a middle

Combines both for an inclusive range and knows atMost includes zero.

for a senior

Chooses ranges over exact counts to keep retry/batch tests robust and explains the failure semantics.

for a principal

Sets team conventions on when to assert exact counts vs bounded ranges, balancing precision against test brittleness.

## The range modes MockK's `verify` exposes three count parameters that can be mixed: - `exactly = n` — precisely n matching calls. - `atLeast = n` — n **or more** matching calls (the lower bound; this is the bare-`verify` default with n = 1). - `atMost = n` — n **or fewer** matching calls, **including zero** (the upper bound). ```kotlin val client = mockk<HttpClient>(relaxed = true) retrier.run { client.send(req) } // retries on failure verify(atLeast = 1) { client.send(req) } // happened at least once verify(atMost = 3) { client.send(req) } // no more than 3 attempts verify(atLeast = 1, atMost = 3) { client.send(req) } // inclusive range 1..3 ``` ## Inclusive range Passing **both** `atLeast` and `atMost` asserts the count lies in the inclusive interval `[atLeast, atMost]`. This is the canonical way to express "between 1 and 3 retries". ## `atMost` includes zero A subtlety: `verify(atMost = 2)` **passes if the call never happened**, because 0 ≤ 2. If you mean "called, but no more than twice", combine with `atLeast = 1`. ## What failure looks like If the recorded count violates a bound, MockK throws an `AssertionError` whose message reports the **matcher**, the number of matching invocations found, and the bound that was breached. Exceeding `atMost` therefore fails just like falling short of `atLeast`. ## Relation to `exactly` `exactly = n` is conceptually `atLeast = n` **and** `atMost = n` simultaneously. Don't combine `exactly` with the range params — pick one expression of intent. ## When to prefer ranges Exact counts make tests **brittle** when the production code legitimately makes a bounded-but-variable number of calls (retries, batching, polling). `atLeast`/`atMost` capture the **contract** ("at least one, at most three") without over-specifying, so the test survives benign refactors.

  • Does verify(atMost = 2) pass if the method was never called?
    Yes. Zero is ≤ 2, so it passes. Add atLeast = 1 if you require at least one call.
  • Can you write verify(exactly = 2, atMost = 3)?
    No — exactly already pins both bounds. Mixing it with atLeast/atMost is contradictory; use one or the other.

saying these in an interview costs you the question

  • Assuming atMost = n fails when the call count is zero
  • Combining exactly with atLeast/atMost
  • Thinking atLeast = 0 is meaningful (it always passes)
  • Using exact counts for retry logic, making tests flaky-brittle

context