Skip to main content
Version: 5.2

Custom Matchers

It is easy to define your own matchers in Kotest.

Simply extend the Matcher<T> interface, where T is the type you wish to match against. The Matcher interface specifies one method, test which returns an instance of MatcherResult.

interface Matcher<in T> {
fun test(value: T): MatcherResult
}

This MatcherResult type defines three methods - a boolean to indicate if the test passed or failed, and two failure messages.

interface MatcherResult {
fun passed(): Boolean
fun failureMessage(): String
fun negatedFailureMessage(): String
}

The first failure message is the message to the user if the matcher predicate failed. Usually you can include some details of the expected value and the actual value and how they differed. The second failure message is the message to the user if the matcher predicate evaluated true in negated mode. Here you usually indicate that you expected the predicate to fail.

The difference in those two messages will be clearer with an example. Let's consider writing a length matcher for strings, to assert that a string has a required length. We will want our syntax to be something like str.shouldHaveLength(8).

Then the first message should be something like "string had length 15 but we expected length 8". The second message would need to be something like "string should not have length 8"

First we build out our matcher type:

fun haveLength(length: Int) = Matcher<String> {
return MatcherResult(
value.length == length,
{ "string had length ${value.length} but we expected length $length" },
{ "string should not have length $length" },
)
}

Notice that we wrap the error messages in a function call so we don't evaluate if not needed. This is important for error messages that take some time to generate.

This matcher can then be passed to the should and shouldNot infix functions as follows:

"hello foo" should haveLength(9)
"hello bar" shouldNot haveLength(3)

Extension Variants

Usually, we want to define extension functions which invoke the matcher function for you and return the original value for chaining. This is how Kotest structures the built in matchers, and Kotest adopts a shouldXYZ naming strategy. For example:

fun String.shouldHaveLength(length: Int): String {
this should haveLength(length)
return this
}

fun String.shouldNotHaveLength(length: Int): String {
this shouldNot haveLength(length)
return this
}

Then we can invoke these like:

"hello foo".shouldHaveLength(9)
"hello bar".shouldNotHaveLength(3)