Beyond `any()` and `eq()`, what argument matchers does MockK offer, and how do you choose between them when stubbing or verifying a call?
answer
- literal == eq under the hood
- any() also matches null
- isNull() / isNull(inverse = true)
- less/more/range need Comparable
- and / or / not compose matchers
basics
~10 sMockK 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 sThe 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 linesevery { 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
Name a few beyond any()/eq() — isNull, ofType, range — and know that a bare literal means equality.
Walk the families, note that any() also matches null, and explain choosing the most specific matcher that still expresses the test's claim.
Add erasure limits on ofType, the readability advantage of ordering matchers over predicates, and how over-loose matchers produce green but meaningless tests.
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.