skip to content

Beyond `any()` and `eq()`, what argument matchers does MockK offer, and how do you choose between them when stubbing or verifying a call?

level: middleimportance: must knowfreq 60%

answer

  1. literal == eq under the hood
  2. any() also matches null
  3. isNull() / isNull(inverse = true)
  4. less/more/range need Comparable
  5. and / or / not compose matchers

basics

~10 s

MockK adds neq, isNull() and isNull(inverse = true), ofType<T>(), less/more (with andEquals) and range for Comparables, match { } predicates, the and/or/not combinators, and refEq/nrefEq/cmpEq for reference or compareTo equality.

solid answer

~50 s

The catalog splits into families: - **Constant**: `any()` matches anything, including null. - **Equality**: `eq(value)`, `neq(value)`, plus `refEq`/`nrefEq` for reference identity and `cmpEq` for `compareTo`-based equality. A bare literal in a recording block is implicitly `eq`. - **Nullability**: `isNull()`, and `isNull(inverse = true)` for "not null". - **Type**: `ofType<T>()` matches by the argument's runtime type — handy for sealed hierarchies and overloads. - **Ordering** (needs `Comparable`): `less(v)`, `more(v)`, each with `andEquals = true`, and `range(from, to, fromInclusive, toInclusive)`. - **Predicate**: `match { it.total > 0 }`, `matchNullable { }` for nullable parameters, `coMatch { }` for suspending predicates. - **Combinators**: `and(a, b)`, `or(a, b)`, `not(m)`. Choose the most specific matcher that still expresses intent: `eq` when the exact value is the point, `ofType`/`range` when a class of values is, `any()` when the argument is genuinely irrelevant. Over-loose matchers make green tests meaningless; over-tight ones make them brittle.

code

kotlin · 6 lines
kotlin
every { repo.find(ofType<Uuid>()) } returns user
every { rates.between(range(1, 10), any()) } returns 0.5
every { audit.log(match { it.actor == "admin" }) } just Runs

verify { repo.save(neq(User.EMPTY)) }
verify { cache.put(isNull(inverse = true), any()) }

go deeper

for a junior

Name a few beyond any()/eq() — isNull, ofType, range — and know that a bare literal means equality.

for a middle

Walk the families, note that any() also matches null, and explain choosing the most specific matcher that still expresses the test's claim.

for a senior

Add erasure limits on ofType, the readability advantage of ordering matchers over predicates, and how over-loose matchers produce green but meaningless tests.

for a principal

Frame matcher precision as a suite-wide property: too loose and tests stop catching regressions, too tight and every refactor churns the suite.

## Why matchers exist Inside `every { }` and `verify { }` MockK is *recording*, not calling. Each argument position is stored as a **matcher** — a predicate over the value that will arrive at call time. A plain literal is not special-cased magic: MockK wraps it as an equality matcher. So `every { svc.find(7) }` is `every { svc.find(eq(7)) }`. Everything else in the catalog is a different predicate in that same slot. ## The catalog by family **Constant.** `any()` matches every argument. It is a constant-true predicate, which is why it also matches `null` on nullable parameters — a detail that surprises people who expect "any non-null value". **Equality.** `eq(value)` is the default. `neq(value)` matches everything except the given value. `refEq(value)` compares by reference identity rather than `equals`, and `nrefEq(value)` is its negation. `cmpEq(value)` compares with `compareTo`, so it matches values that are "equal enough" for ordering even when `equals` disagrees. `eq` also accepts an `inverse` flag, which is another way to spell `neq`. **Nullability.** `isNull()` matches only null; `isNull(inverse = true)` matches only non-null. Both are more precise than reaching for `any()` and hoping. **Type.** `ofType<T>()` (or `ofType(SomeClass::class)`) matches when the argument is an instance of the given type. It shines with sealed classes and polymorphic events: `every { handler.on(ofType<PaymentFailed>()) } returns Unit`. Because it inspects the runtime class, generic type arguments are erased — `ofType<List<String>>()` really only asserts "is a List". **Ordering.** For `Comparable` arguments: `less(v)`, `more(v)`, each accepting `andEquals = true` to include the bound, and `range(from, to, fromInclusive = true, toInclusive = true)` for an interval. These express intent far better than a `match { }` lambda doing the same arithmetic, and they print readably in failure output. **Predicate.** `match { it.userId == 7L }` runs your lambda against the incoming argument. `matchNullable { }` is the variant whose lambda receives a nullable value, needed when the parameter type itself is nullable. `coMatch { }` exists for predicates that must suspend. Predicates are the universal fallback — and the least self-describing, because the failure report can only show that *a* predicate did not match. **Combinators.** `and(left, right)`, `or(left, right)` and `not(matcher)` compose matchers, so `and(more(0), less(100))` is legal, as is `not(isNull())`. Two more families belong to neighbouring concerns: capture matchers store the argument for later assertion, and vararg matchers deal with variadic parameters. ## Choosing The governing question is *what does this test actually claim?* - If the exact value is the contract — an id, an amount, a status — use `eq` (or just the literal). A test that says `any()` where the value matters passes for a bug that sends the wrong id. - If a **class** of values is the contract, name that class: `ofType<Retryable>()`, `more(0)`, `range(1, 10)`. This is the sweet spot — it documents intent and does not break when an unrelated field changes. - If the argument is genuinely irrelevant to this assertion, `any()` is honest and better than an over-specified value that will churn. - Reach for `match { }` only when nothing simpler fits, and prefer to extract it into a named extension so the intent has a name. ## Practical notes - Matchers are recorded per argument position; a call being matched has to satisfy all of them. - The same catalog works in `every`, `coEvery`, `verify`, `coVerify` and the ordering verifications — matchers are a property of the recording model, not of stubbing specifically. - Matchers only mean anything inside those recording blocks; computing one outside and passing it in does not work, and mixing raw literals with matchers in the same call has its own rule. - When a stubbed call "doesn't fire", the fastest diagnosis is usually that a matcher was too tight: an `eq` on an object whose class has no `equals`, or an `ofType` defeated by erasure. ## A worked example ```kotlin every { audit.log(ofType<SecurityEvent>(), more(0L)) } just Runs verify { audit.log(match { it.actor == "admin" }, any()) } ``` The stub says "any security event with a positive timestamp", and the verification says "one of them came from admin, timestamp irrelevant" — two different precisions on the same method, each chosen to match what the test claims.

  • Does MockK's `any()` match a null argument?
    Yes. `any()` is a constant-true matcher, so on a nullable parameter it accepts null just as readily as a value. If the test means "some non-null argument", the correct matcher is `isNull(inverse = true)`, and if it means "null specifically", it is `isNull()`.
  • When would you use `ofType<T>()` rather than `any()`?
    When the method takes a supertype and the test is about one concrete subtype — sealed event hierarchies are the classic case. `ofType<PaymentFailed>()` documents that the stub applies only to that branch and lets you register a different answer for siblings. Remember it checks the runtime class, so generic parameters are erased.

saying these in an interview costs you the question

  • Believing MockK only offers any() and eq().
  • Assuming any() excludes null.
  • Using match { } for everything instead of the specific ordering or type matchers.
  • Thinking ofType<T>() checks generic type arguments.
  • Claiming matchers are only usable in every, not in verify.

context