Core
The core module defines the MatcherField<T, R> interface and all extension functions that turn fields into matchers.
- Kotest
- Hamkrest
MatcherField<T, R>
interface MatcherField<T, R> {
val name: String
fun extract(value: T): R?
val description: String
}
| Member | Description |
|---|---|
name | Identifier used as the basis for description. |
extract(value: T): R? | Pulls the field's value out of a subject of type T. Returns null when absent. |
description | Human-readable label used in failure messages. Derived from name by default. |
Description derivation
The default description splits name on camelCase boundaries, drops any leading article (a, an, the), and drops a trailing Field token:
name | description |
|---|---|
aProviderCode | Provider Code |
anAddressPostcode | Address Postcode |
aFooField | Foo |
firstProfileType | First Profile Type |
For JSON fields, description is overridden to return the JSONPointer path directly (e.g. /profiles/0/supplier), since the path is already unique and readable.
Extension functions
of
infix fun <T : Any, R> MatcherField<T, R>.of(expected: R?): Matcher<T?>
Equality check. The most common operator.
anAddressPostcode of fixtures[postcode]
serviceable of true
profileCount of 3
matching(matcher)
infix fun <T : Any, R> MatcherField<T, R>.matching(matcher: Matcher<R?>): Matcher<T?>
Delegates to any matcher from your chosen library.
- Kotest
- Hamkrest
anAmount matching beGreaterThan(0)
profileTypes matching containExactlyInAnyOrder("FTTC")
anAmount matching greaterThan(0)
profileTypes matching hasElement("FTTC")
matching(matcher, vararg others)
fun <T : Any, R> MatcherField<T, R>.matching(matcher: Matcher<R?>, vararg others: Matcher<R?>): Matcher<T?>
Composes multiple matchers against the same field. All must pass.
- Kotest
- Hamkrest
Uses Matcher.all. Equivalent to chaining .and() but scoped to one field.
anAmount.matching(beGreaterThan(0), beLessThan(1000))
Uses hamkrest allOf. Equivalent to chaining and but scoped to one field.
anAmount.matching(greaterThan(0), lessThan(1000))
matching(expectedRegex) — String fields only
infix fun <T : Any> MatcherField<T, String>.matching(expectedRegex: String): Matcher<T?>
Matches the extracted string against a regular expression.
- Kotest
- Hamkrest
Applies Kotest's match(regex).
aSku matching "[A-Z]{3}-\\d+"
Applies present(matches(Regex(...))). A null extracted value produces a mismatch rather than throwing.
aSku matching "[A-Z]{3}-\\d+"
withListOf
fun <T : Any, R> MatcherField<T, List<R>>.withListOf(vararg expected: R): Matcher<T?>
Exact ordered list equality. The field must extract a List<R>.
profileDescriptions.withListOf("Full Fibre 900", "Full Fibre 500")
- Kotest
- Hamkrest
Uses Kotest containExactly.
Uses equalTo(list). Elements must be in the same order. Hamkrest has no containExactly combinator.
withSetOf
fun <T : Any, R> MatcherField<T, Set<R>>.withSetOf(vararg expected: R): Matcher<T?>
Order-insensitive set equality. The field must extract a Set<R>.
supportedProtocols.withSetOf("FTTC", "FTTP")
- Kotest
- Hamkrest
Uses Kotest containExactlyInAnyOrder.
Uses equalTo(set). Set.equals is structurally order-insensitive, so equalTo works correctly here.
Phrasing sugar
These functions are in Phrases.kt alongside the core extension functions. They add no logic — they make assertion call-sites read like English.
with
infix fun <T> Matcher<T>.with(other: Matcher<T>): Matcher<T>
Composes two matchers (both must pass). An alternative to .and() / and when you want the call-site to read as a sentence.
(aProviderCode of "FW") with (aServiceType of "FTTP")
thatHas(matcher)
fun <T> thatHas(matcher: Matcher<T>): Matcher<T>
Identity wrapper. Use it to open a multi-line assertion block that reads like prose.
assertThat(response, thatHas(aProviderCode of "FW"))
thatHas(matcher, vararg others)
fun <T> thatHas(matcher: Matcher<T>, vararg matchers: Matcher<T>): Matcher<T>
Composes multiple matchers — all must pass.
- Kotest
- Hamkrest
Uses Matcher.all.
assertThat(response, thatHas(
aProviderCode of "FW",
aServiceType of "FTTP",
aDate of "2026-01-01"
))
Uses allOf.
assertThat(response, thatHas(
aProviderCode of "FW",
aServiceType of "FTTP",
aDate of "2026-01-01"
))
thatIs
fun <T> thatIs(matcher: Matcher<T>): Matcher<T>
Identity wrapper, same as thatHas. Use whichever reads more naturally at the call-site.
assertThat(status, thatIs(aStatus of "SERVICEABLE"))
shouldHaveAll
fun <T> T?.shouldHaveAll(vararg matchers: Matcher<T?>)
Extension on the subject. Asserts that it passes every supplied matcher.
- Kotest
- Hamkrest
Calls should(Matcher.all(...)).
response.shouldHaveAll(
aProviderCode of "FW",
aServiceType of "FTTP"
)
Calls assertThat(this, allOf(...)).
response.shouldHaveAll(
aProviderCode of "FW",
aServiceType of "FTTP"
)
Extension points
toMatcher
fun <T : Any, R, OUT> MatcherField<T, R>.toMatcher(expected: OUT?, convertActual: (R) -> OUT?): Matcher<T?>
Use when you have a domain value type and want a custom infix function instead of of. convertActual transforms the extracted value before the equality check, so expected can be a domain type different from R.
// Define once alongside the field:
infix fun <T : Any> MatcherField<T, ProviderCode>.withValue(expected: String) =
toMatcher(expected) { it.code }
// In tests:
aProviderCode withValue "FW"
nullableExtractingMatcher / extractingMatcher
The building block used internally by all extension functions. Exposed for writing field-aware matchers that do not fit the MatcherField interface.
- Kotest
- Hamkrest
fun <T : Any, R> nullableExtractingMatcher(
name: String,
extractValue: (T) -> R?,
match: Matcher<R?>
): Matcher<T?>
match receives null when the subject is null or extraction returns null. Extraction exceptions are surfaced with name in the message.
fun <T : Any, R> extractingMatcher(
name: String,
extractValue: (T) -> R?,
matcher: Matcher<R?>
): Matcher<T?>
The returned matcher's description is prefixed with name, so failure output always identifies which field failed.
val myMatcher = extractingMatcher(
name = "Order Status",
extractValue = { order: Order -> order.status },
matcher = equalTo("CONFIRMED")
)
assertThat(order, myMatcher)