How does MockK's `eq()` decide whether an argument matches, and when would you reach for `refEq()` or `cmpEq()` instead?
answer
- literal → eq → deep structural
- arrays compared by content
- no equals() → identity → stub silently misses
- refEq = same instance on purpose
- cmpEq: BigDecimal 1.0 vs 1.00
basics
~10 seq() 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.
solid answer
~50 s`eq(value)` — which is also what a bare literal becomes — is a **deep structural** comparison. For arrays MockK compares contents rather than references, so `eq(arrayOf(1, 2))` matches an equal array; for everything else it delegates to `equals()`. That delegation is the thing to reason about. Data classes match nicely because they have generated `equals`. A class without `equals` falls back to identity, so `eq(SomeRequest(...))` fails against a freshly-constructed but logically identical argument — one of the most common "my stub doesn't fire" causes. Mocks themselves use identity equality, so `eq(someMock)` is effectively a reference check. `refEq(value)` forces identity even when `equals` would say yes — use it when the test asserts *this very instance* was passed through. `nrefEq` is the negation. `cmpEq(value)` uses `compareTo`. The canonical case is `BigDecimal("1.0")` vs `BigDecimal("1.00")`: `equals` says false because scale differs, `compareTo` says zero. `cmpEq` matches; `eq` does not.
code
kotlin · 3 linesevery { crypto.sign(eq(byteArrayOf(1, 2, 3))) } returns sig // arrays by content
verify { pool.release(refEq(connection)) } // same instance
verify { ledger.post(cmpEq(BigDecimal("1.0"))) } // 1.00 matches toogo deeper
Know that a plain literal means equality and that MockK falls back to the class's equals.
Explain the deep/array handling, the equals delegation, and name refEq and cmpEq with a concrete case for each.
Diagnose from symptoms — missing equals, arrays nested in data classes, BigDecimal scale, mutated arguments — and pick between eq/refEq/cmpEq/predicate deliberately.
Treat argument equality as a design property: value types with real equality make tests simple, and tests that need exotic matchers are often pointing at weak domain types.
## What `eq` actually does When MockK records `every { svc.save(order) }`, the literal `order` is wrapped into an equality matcher. At call time the matcher compares the incoming argument with the recorded one using MockK's deep-equality helper, which: - compares **arrays by content**, recursively (so nested arrays compare element-wise rather than by reference), and - otherwise delegates to the argument's `equals()`. The array behaviour is worth knowing because Java/Kotlin arrays use identity `equals`, so a naive matcher would never match a `ByteArray` argument. MockK's structural handling is what makes `every { crypto.sign(eq(byteArrayOf(1, 2, 3))) }` usable at all. ## Everything else is your `equals` For non-array types, matching is exactly as good as the class's `equals` implementation: - **Kotlin data classes**: generated component-wise `equals` — matching works as you expect, provided every property is itself well-behaved. - **Ordinary classes without `equals`**: inherited identity semantics. `eq(Request("a"))` will not match a different `Request("a")` instance, because they are not the same object. This produces the classic symptom: the stub silently does not apply, and a strict mock reports *no answer found* showing two arguments that print identically. - **Classes with a partial `equals`**: matching follows whatever the class decided to ignore, which can make a test pass for an argument that differs in an ignored field. - **Mocks as arguments**: mocks do not override `equals`, so identity applies. Passing the same mock instance matches; a different mock of the same type does not. - **Arrays nested inside data classes**: the generated `equals` uses array identity, so two data class instances holding equal-but-distinct arrays are *not* equal. MockK's deep comparison applies to the top-level argument, not to fields inside an object whose own `equals` it defers to. ## `refEq` — identity on purpose `refEq(value)` matches only when the argument is the *same object*, regardless of `equals`. Use it when instance identity is part of the contract: a connection handed back to the pool, a listener unregistered, a cached instance passed through untouched rather than copied. `nrefEq(value)` is the negation — "anything but that instance". Without `refEq`, a test on value-equal-but-distinct objects passes even when the code cloned or rebuilt the object, which may be exactly the defect you are hunting. ## `cmpEq` — equality by ordering `cmpEq(value)` matches when `compareTo` returns zero. Some types deliberately have `equals` narrower than `compareTo`: ```kotlin BigDecimal("1.0") == BigDecimal("1.00") // false: scale differs BigDecimal("1.0").compareTo(BigDecimal("1.00")) // 0 ``` A money-handling test that stubs `every { ledger.post(eq(BigDecimal("1.0"))) }` will not match a production value of `1.00` computed from arithmetic. `cmpEq(BigDecimal("1.0"))` matches both. The same reasoning applies to any `Comparable` whose ordering is coarser than its equality. The flip side: `cmpEq` requires a total ordering to be meaningful, and it silently accepts values `equals` would reject — so use it where ordering *is* the semantics (numbers, versions, timestamps), not as a general looseness dial. ## Diagnosing equality-driven mismatch When a stub does not fire, or a `verify` fails with arguments that look identical in the message, run this checklist: 1. Does the argument's class implement `equals`? If not, that is the answer — either add it, switch to `match { }` on the fields you care about, or capture and assert. 2. Is a field an array, or a type with identity equality nested inside a data class? Same problem, one level down. 3. Is the type `BigDecimal` or another scale/precision-sensitive value? Reach for `cmpEq`. 4. Is the argument a mock? Then only the same instance matches. 5. Is the class mutable and mutated after the call? A structural matcher compares at match time, so late mutation can make a previously-matching argument fail during verification. That last point deserves emphasis: verification runs after the fact, against the recorded argument *reference*. If production code mutates the object after passing it, `eq` sees the mutated state. ## Choosing between them Default to `eq` (or the bare literal) when the class has value semantics. Use `refEq` when identity is the assertion. Use `cmpEq` for `Comparable` types whose `equals` is stricter than their ordering. When none of them fits — because the class has no `equals` and you only care about two fields — a predicate matcher on those fields is clearer than fabricating an `equals` for the sake of a test.
- Why does `eq` on a data class holding a ByteArray field often fail?MockK's structural comparison applies to the top-level argument; for a data class it then defers to that class's `equals`, and a generated data class `equals` compares array fields by reference. Two instances holding equal-but-distinct arrays are therefore unequal. The usual fixes are a hand-written `equals`, wrapping the bytes in a value type with proper equality, or matching on the fields with a predicate.
- A verification fails even though the argument printed in the error looks identical to the expected one. What are the likely causes?Most often the class has no `equals`, so identity is being compared and two distinct instances that print the same are unequal. The other frequent cause is mutation: verification compares against the recorded reference, so if production code changed the object after the call, its current state no longer matches. Capturing the argument or asserting on specific fields disambiguates quickly.
saying these in an interview costs you the question
- Thinking `eq` uses reference identity by default.
- Assuming arrays match under `eq` only if they are the same instance.
- Expecting `eq(BigDecimal("1.0"))` to match `BigDecimal("1.00")`.
- Believing mocks compare structurally when passed as arguments.
- Adding an `equals` override to production classes solely to make MockK matching work, without considering a predicate matcher.