skip to content

What makes Hamcrest produce its readable mismatch messages? Explain the roles of matches, describeTo, and describeMismatch.

level: seniorimportance: should knowfreq 40%

answer

  1. Matcher = matches + describeTo + describeMismatch
  2. describeTo → 'Expected:' line; describeMismatch → 'but:' line
  3. SelfDescribing is the parent interface
  4. BaseMatcher gives default 'was <x>' mismatch
  5. TypeSafeMatcher does null/cast → matchesSafely

basics

~20 s

Each matcher implements matches (does the value pass?), describeTo (write what was expected), and describeMismatch (write what actually happened). assertThat calls matches; if it's false it asks the matcher to describe both sides, giving the 'Expected: ... but: was ...' message.

solid answer

~40 s

Hamcrest's readable failures come from the Matcher interface (via SelfDescribing). A matcher implements three things: matches(item) returns whether the value satisfies it; describeTo(Description) appends a human description of the *expectation* ('a string containing "err"'); and describeMismatch(item, Description) appends what actually happened ('was "ok"'). assertThat first calls matches; on false it builds a Description, calls describeTo for the 'Expected:' line and describeMismatch for the 'but:' line, then throws AssertionError with that text. The base class BaseMatcher provides a default describeMismatch that prints 'was <item>', and TypeSafeMatcher handles the null/cast boilerplate and lets you override matchesSafely/describeMismatchSafely. This contract — every matcher can describe itself and the mismatch — is exactly why composed matchers (allOf, hasItem) still yield precise messages: the outer matcher delegates description to its children.

go deeper

for a junior

Recognises the 'Expected ... but: was ...' format and that the matcher produces it.

for a middle

Names matches/describeTo/describeMismatch and knows BaseMatcher/TypeSafeMatcher exist.

for a senior

Explains the full contract and how assertThat orchestrates it, and how composition delegates descriptions.

for a principal

Designs matcher hierarchies for the domain, ensures mismatch messages are diagnostic, and standardises base-class usage.

## The interfaces behind the messages Hamcrest's nice failures are not magic in `assertThat`; they come from a small contract every matcher obeys. **`SelfDescribing`** — one method, `describeTo(Description)`. Anything self-describing can append a textual description of *itself* to a `Description` (a string-builder-like sink). **`Matcher<T>` extends `SelfDescribing`** and adds: - `boolean matches(Object item)` — does the value satisfy the condition? - `void describeMismatch(Object item, Description mismatchDescription)` — explain what was wrong with this specific item. - (a deprecated `_dont_implement_Matcher___instead_extend_BaseMatcher` marker that nudges you to subclass `BaseMatcher`.) So each matcher carries **three behaviours**: *test* (`matches`), *describe the expectation* (`describeTo`), *describe the actual* (`describeMismatch`). ## What assertThat does, step by step ``` if (!matcher.matches(actual)) { Description d = new StringDescription(); d.appendText("\nExpected: ").appendDescriptionOf(matcher) // -> describeTo .appendText("\n but: "); matcher.describeMismatch(actual, d); // -> describeMismatch throw new AssertionError(d.toString()); } ``` That is why you see: ``` Expected: a string containing "err" but: was "ok" ``` The first line is the matcher's `describeTo`; the second is its `describeMismatch`. ## The helper base classes - **`BaseMatcher<T>`** — implements the marker and a *default* `describeMismatch` that simply appends `"was " + item`. You still must implement `matches` and `describeTo`. - **`TypeSafeMatcher<T>`** — the recommended base. It handles the boring, error-prone parts: it null-checks and type-checks the item, casts it for you, and exposes `matchesSafely(T)` and `describeMismatchSafely(T, Description)` with the correctly-typed argument. It also returns a clean mismatch when the type is wrong instead of throwing `ClassCastException`. ## Why composition stays readable Because description is part of the contract, a *combining* matcher just delegates. `allOf` asks each child to `describeTo`, joining with 'and'; `hasItem` asks its element-matcher to describe itself, etc. So even deep nestings produce precise messages without special code in `assertThat`. ## The `Description` object A `Description` is an append-only text sink with helpers: `appendText`, `appendValue(obj)` (renders a value as `<...>` with type-aware quoting), `appendDescriptionOf(selfDescribing)` (delegates to a child's `describeTo`), and `appendList(...)`. `StringDescription` is the concrete one used for messages. ## Terms - *Interface / contract*: a set of method signatures a class must implement. - *Sink*: an object you append output to. - *Cast*: convert a reference to a more specific type. - *Marker method*: a method whose presence (not behaviour) signals intent — here, to extend BaseMatcher. ## Practical upshot When you write a custom matcher you implement `matchesSafely` and `describeTo`, and optionally `describeMismatchSafely`. If you forget `describeMismatch`, you still get a usable default ('was <x>') from BaseMatcher — but a tailored mismatch message is what makes a custom matcher worth writing.

  • What does TypeSafeMatcher do for you over BaseMatcher?
    It null-checks and type-checks the item, casts it to T, and gives you typed matchesSafely/describeMismatchSafely; a wrong type yields a clean mismatch instead of a ClassCastException.
  • Which method produces the 'Expected:' part of the message versus the 'but: was' part?
    describeTo produces the expectation ('Expected:'), and describeMismatch produces the actual-value part ('but: was ...').

saying these in an interview costs you the question

  • Thinking assertThat hand-builds messages — the matcher describes itself
  • Believing matches() also prints the message (it only returns boolean)
  • Implementing Matcher directly instead of extending BaseMatcher/TypeSafeMatcher
  • Assuming you must always override describeMismatch (BaseMatcher has a default)

context