skip to content

Argument Matchers

The matcher catalog beyond any() and eq(). Interviewers ask for match { } and typed matchers to see if you can express precise expectations instead of over-matching everything.

on this pageshow

explore

questions

5

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

open as a page

How does MockK's `eq()` decide whether an argument matches, and when would you reach for `refEq()` or `cmpEq()` instead?

level: middleimportance: should knowfreq 45%

basics

~10 s

eq() compares structurally: arrays are compared by content, everything else by equals(). refEq() compares by reference identity, and cmpEq() compares with compareTo, which matters for types like BigDecimal where equals and compareTo disagree.

open as a page

In MockK, how do you express "this argument must be null", "must not be null", and "must be this concrete subtype" — and what are the limits of type-based matching?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use isNull() for null and isNull(inverse = true) for not-null; any() will not do, since it matches null too. Use ofType<T>() for a concrete subtype — it checks the runtime class, so generic arguments are erased.

open as a page

MockK's `match { }` lets you supply an arbitrary predicate as an argument matcher. What are the mechanics and the downsides, and how do you keep predicate-based matching debuggable?

level: seniorimportance: should knowfreq 35%

basics

~20 s

match { } stores your lambda as the matcher and runs it against each candidate argument at match time. The downside is diagnostics: a failing lambda prints as an opaque matcher. Keep predicates small, pure, and named via reusable extensions, or use specific matchers instead.

open as a page

When a mocked call takes a large request object, how do you decide between matching it with `eq` on the whole object, a `match { }` predicate on a few fields, or `any()` — and what policy would you set for a team?

level: principalimportance: should knowfreq 25%

basics

~20 s

Match on exactly what the test claims. eq on the whole object when the object is the contract and has real value equality; a narrow predicate when only some fields are the claim; any() when the argument is irrelevant here and asserted elsewhere. Never let looseness be accidental.

open as a page