When you combine two Kotest matchers with `and` or `or`, how is the combined MatcherResult produced — which sub-matcher's failure message do you actually see, and what does the combination cost you in evaluation and message quality?
answer
- and: first failure wins, short-circuits
- or: first pass wins, else last failure's message
- messages are quoted verbatim, never merged
- value may be tested twice → keep matchers pure
- named domain rule → hand-write the matcher
basics
~20 sBoth combinators short-circuit and return one sub-result verbatim. and returns the first failure, else the second result; or returns the first pass, else the last failure. So you see one side's message, never an assembled "A and B" sentence — and the value may be tested twice.
solid answer
~50 s`and` and `or` in Kotest build a new `Matcher` that delegates. `a and b`: test `a`; if it fails, return **a's** result unchanged and never touch `b`; otherwise return `b`'s result. `a or b`: test `a`; if it passes, return that result and never touch `b`; otherwise return `b`'s result. Two consequences matter in practice. First, the failure message is always **one** sub-matcher's message, quoted verbatim — there is no combined sentence. A failing `or` therefore reports only the last alternative ("5 should be even") and says nothing about the alternative that also failed, which reads as a lie about the real rule. Second, the value is passed to `test` up to twice, so matchers with side effects or expensive extraction are called repeatedly. Rule of thumb: use `and`/`or` for quick ad-hoc combinations inside a test; when the composite is a named domain rule that a whole team will read failures from, write a single purpose-built matcher whose message states the whole rule.
code
kotlin · 6 linesval positive = Matcher<Int> { MatcherResult(it > 0, { "$it should be > 0" }, { "$it should not be > 0" }) }
val even = Matcher<Int> { MatcherResult(it % 2 == 0, { "$it should be even" }, { "$it should not be even" }) }
3 should (positive and even) // fails: "3 should be even" (positive passed, fell through)
-3 should (positive and even) // fails: "-3 should be > 0" (short-circuit; even never ran)
5 should (positive or even) // passes: positive passed, even never rango deeper
Know that and/or build a new matcher and that you see one side's message, not a merged one.
State the short-circuit rules precisely for both combinators and predict which message a given expression produces.
Add double evaluation, purity requirements, operand ordering, and when to hand-write a composite for message quality.
Treat failure-message quality as an interface the team consumes; set the boundary between ad-hoc composition in a test and named domain matchers with owned diagnostics.
## What the combinators actually build `Matcher<T>` exposes `and` and `or` as infix functions that return a **new** `Matcher<T>` wrapping the two operands. There is no clever merging: each combinator picks one of the two `MatcherResult`s and returns it unchanged. - **`a and b`** — run `a.test(value)`. If it did **not** pass, return that result immediately; `b` is never evaluated. If it passed, return `b.test(value)`. - **`a or b`** — run `a.test(value)`. If it **passed**, return that result immediately; `b` is never evaluated. If it failed, return `b.test(value)`. Both are short-circuiting, exactly like `&&` and `||` on booleans — the difference is that the thing being propagated is not a boolean but a whole result object carrying two message lambdas. ## Which message you see Because the returned result is a sub-result verbatim, the failure text is always **one** matcher's wording: - `3 should (positive and even)` fails with `even`'s message (`positive` passed, so the composite fell through to `even`). - `-3 should (positive and even)` fails with `positive`'s message; `even` was never consulted, so nothing in the output hints that `-3` is also odd. - `5 should (negative or even)` fails with `even`'s message only — the report names the *last* alternative as if it were the whole rule, which actively misleads a reader who does not have the test source open. This is the single most important thing to know about composition: **`and`/`or` compose the predicate, not the explanation.** The predicate is correct; the diagnostics degrade. ## Double evaluation A composite may call `test(value)` twice on the same value. Three practical consequences: 1. A matcher that mutates state, consumes an iterator or a stream, or counts invocations will misbehave — the second matcher sees the drained value. 2. A matcher that does expensive extraction (parsing, hashing, a database round trip — which should not be in a matcher anyway) pays twice. 3. A matcher over a non-idempotent source (a random generator, a clock) can pass one branch and fail the other for reasons unrelated to the rule under test. Matchers should therefore be **pure**: take a value, compute, return a result. ## Negation over composites `(a and b).invert()` is logically what De Morgan predicts — it passes when at least one of `a`, `b` fails — because inversion flips the propagated `passed()` flag. But the **message** is the swapped message of whichever branch decided the outcome, so the output frequently reads as a statement about half the rule. Inverting a composite is a correctness-preserving, readability-destroying operation. ## When composition is the right tool Use `and`/`or` when: - the combination is local and ad hoc — one test asserting two independent, already well-named properties; - both sub-matchers already produce messages that stand alone; - ordering the operands puts the cheapest and most discriminating check first (`and` short-circuits, so lead with the check most likely to fail). Write a dedicated matcher instead when: - the composite has a **name in the domain** ("is a shippable order") — then the failure message should say that name and then say which sub-condition failed; - you need `or` semantics with a decent message — a hand-written matcher can evaluate both branches and report "expected X to be either A or B, but it was neither (not A because …, not B because …)"; - the sub-checks share expensive work you want to do once. A hand-written composite is barely more code: one `Matcher { value -> … }` that evaluates the sub-matchers itself, collects their results, and builds a message from the failing ones. That gives you the combined sentence `and`/`or` cannot produce. ## Operator ordering `and` and `or` are ordinary infix functions with the same precedence, so mixed chains bind left to right and are **not** disambiguated by boolean-style precedence rules. Parenthesise mixed expressions explicitly; `a and b or c` does not mean what a reader trained on `&&`/`||` precedence assumes. ## Bottom line Composition gives you a correct predicate for free and a mediocre failure message for free with it. Accept that inside a single test; refuse it for a matcher the whole team will read CI output from.
- How would you get a failure message that reports every violated condition instead of just one?Write a single matcher that evaluates each condition itself, collects the ones that failed into a list, and builds one message naming all of them. `and` cannot do this because it returns the first failing sub-result and never evaluates the rest, so the information simply does not exist by the time the composite reports.
- Does the order of operands around `and` matter?Semantically no — the predicate is the same either way. Practically yes: `and` short-circuits, so the left operand is the only one guaranteed to run and its message is the one you see when it fails. Put the cheapest, most discriminating precondition first so the report names the root cause rather than a downstream symptom.
saying these in an interview costs you the question
- Expecting the composite to print a combined "A and B" failure message
- Assuming both sub-matchers always run, so side-effecting matchers are safe
- Thinking `or` reports both alternatives when both fail
- Assuming `and`/`or` follow boolean operator precedence in mixed chains
- Reaching for deep and/or chains for a rule that deserves its own named matcher