skip to content

Kotest's Matcher type exposes an `invert()` function, and any matcher can also be used through `shouldNot`. How does negation actually work at the MatcherResult level, and when do you need invert() rather than just calling shouldNot?

level: middleimportance: should knowfreq 24%

answer

  1. shouldNot = negate at the call site
  2. invert() = new Matcher, passed flipped, messages swapped
  3. invert() when the negation must be a value
  4. val beOdd = beEven().invert()
  5. double negation and inverted composites read badly

basics

~20 s

shouldNot negates at the call site: it fails when the matcher passes, printing negatedFailureMessage. invert() negates the matcher itself, returning a new Matcher whose passed flag is flipped and whose two messages are swapped — needed when you must hand a negated matcher to something that takes a Matcher value.

solid answer

~50 s

There are two places negation can live. `value shouldNot matcher` negates at the **call site**: the entry point raises a failure when `passed()` is `true` and prints `negatedFailureMessage()`. Nothing about the matcher changes. `matcher.invert()` negates the **matcher**: it returns a new `Matcher<T>` whose `test` flips `passed()` and swaps the two messages, so the old negated message becomes the new failure message. The result is a first-class value. You need `invert()` whenever the negated rule has to be *passed around* rather than asserted immediately — feeding it to an inspector or a collection matcher that accepts a `Matcher<T>`, storing it in a named val (`val beOdd = beEven().invert()`), or using it as an operand of `and`/`or`. For a plain one-off assertion, `shouldNot` is simpler and clearer. Inverting a composite is logically correct but produces the swapped message of whichever branch decided the outcome, which usually reads badly.

code

kotlin · 12 lines
kotlin
fun beEven(): Matcher<Int> = Matcher { n ->
    MatcherResult(n % 2 == 0, { "$n should be even" }, { "$n should not be even" })
}

fun Int.shouldBeEven() = this should beEven()
fun Int.shouldNotBeEven() = this shouldNot beEven()

// negation as a reusable value: messages are swapped, so this reads correctly
val beOdd: Matcher<Int> = beEven().invert()

3 should beOdd
listOf(1, 3, 5).forAll { it should beOdd }

go deeper

for a junior

Know that shouldNot exists and that invert() returns a flipped matcher whose messages are swapped.

for a middle

Explain the swap precisely and give a concrete case where a matcher value is required and shouldNot cannot be used.

for a senior

Discuss composition with invert(), the misleading messages from inverted composites, and the discipline of testing both polarities.

for a principal

Tie it back to the report-don't-throw contract: negation is a pure data transformation, which is why it is total and composable — and decide when a negated concept deserves its own named matcher.

## Two places negation can live Kotest lets you express "not" either at the assertion site or inside the matcher, and knowing which is which explains a whole class of confusing failure output. ### Negation at the call site: `shouldNot` `value shouldNot matcher` runs `matcher.test(value)` and raises a failure when the result **passed**, using `negatedFailureMessage()`. The matcher object is untouched — it still describes the positive property; the entry point simply inverts the pass/fail decision and picks the other message. This is why every custom matcher must word both messages: the negative direction is available to callers whether or not you anticipated it. ### Negation inside the matcher: `invert()` `matcher.invert()` returns a **new** `Matcher<T>`. Its `test` calls the original and returns a result where: - `passed()` is the boolean opposite, and - the two messages are **swapped** — the original `negatedFailureMessage` becomes the inverted matcher's `failureMessage`, and vice versa. That swap is the important detail. It is what keeps `x should beEven().invert()` printing sensible text: the message you wrote for "…should not be even" is exactly the message a positive assertion on the inverted matcher needs. ## When you actually need invert() `shouldNot` covers the everyday case. Reach for `invert()` when the negated rule must exist as a **value**: - **Naming a rule.** `val beOdd = beEven().invert()` gives the negation its own name so tests read positively. - **Passing to an API that takes a Matcher.** Inspectors and collection matchers accept a `Matcher<T>` and apply it themselves; you cannot slip a `shouldNot` in there, because `shouldNot` is an assertion, not a matcher. - **Composition.** `and` / `or` operate on matchers, so "positive and not even" is `positive and even.invert()`. There is no `andNot`. - **Building the negative half of a public assertion surface.** A `fun Foo.shouldNotBeBar()` normally just delegates to `shouldNot beBar()`; but if your surface hands out matchers rather than assertions, `invert()` is how the negative variant is produced from the same source of truth. ## Traps **Double negation.** `value shouldNot matcher.invert()` is a positive assertion written the hard way, and its message is the original `failureMessage` — correct but bewildering to read. Collapse it. **Inverting a composite.** `(a and b).invert()` passes when at least one operand fails, which is logically what De Morgan predicts. The *message*, though, is the swapped message of whichever branch decided the outcome, so a reader sees a sentence about half the rule. If a negated composite is a real domain concept, write it as its own matcher with its own wording. **Assuming invert() is the same object.** It returns a new matcher; the original is unaffected. Nothing is mutated, which is what makes it safe to invert a shared matcher instance. **Relying on invert() to fix a lazy negated message.** If you wrote the negated message as a copy of the positive one, `invert()` faithfully propagates the mistake into the positive slot. The swap is mechanical; it cannot repair wording. ## Why the design is like this Because `test()` returns data rather than throwing, negation is a pure transformation of that data — flip a boolean, swap two lambdas. If matchers threw, negation would have to be implemented as exception catching, which cannot distinguish "the rule did not hold" from "the matcher itself blew up". The report-don't-throw contract is what makes both `shouldNot` and `invert()` trivial and total. ## Practical guidance - Default to `shouldNot` in test bodies; it is the most readable form and needs no extra API. - Use `invert()` when the negation is a reusable, nameable rule or must be handed to something expecting a matcher. - Test both directions of every custom matcher — a positive assertion and a negative one — so the negated wording is exercised at least once. - Prefer a purpose-written matcher over inverting a composite when the negative case is a concept your domain actually names.

  • You have `beEven()` and want an assertion for "positive and not even". How do you express it?
    `n should (bePositive() and beEven().invert())`. `and` composes matchers, not assertions, so the negation has to be inside a matcher value — `shouldNot` cannot appear as an operand. Note the failure message will still be whichever operand's swapped message decided the outcome, so if this rule is a real domain concept it is worth writing as one named matcher.

saying these in an interview costs you the question

  • Thinking shouldNot mutates or wraps the matcher rather than negating at the call site
  • Believing invert() reuses failureMessage instead of swapping the two messages
  • Writing `shouldNot m.invert()` and expecting clearer output than a plain `should m`
  • Assuming an inverted composite produces a message describing the whole composite rule
  • Never exercising the negative direction, so a copy-pasted negated message ships unnoticed

context