Skip to main content

Matchers

warning

Please read matchers limitations section!

Regular matchers

Matchers are quite straightforward to use. Instead of literal argument, you have to pass a matcher. You can use named parameters and change their order. Mixing matchers and literal arguments is also allowed.

Full list of matchers with documentation is available here.

Logical matchers

Logical matchers allows combining regular matchers into logical expressions.

everySuspend { repository.findById(or(eq("1"), eq("2"))) } returns stubBook()

Full list of logical matchers is available here.

Vararg matchers

To match a method with varargs you can use regular matchers:

everySuspend { repository.findAllById("1", any(), "3") } returns emptyList()

The problem with regular matchers here is that the number of varargs is always fixed. Answer definition above works only for calls with "1" at index 0, any arg at index 1 and "3" at index 2.

To solve this problem you can use wildcard matchers:

everySuspend { repository.findAllById("1", *anyVarargs(), "3") } returns emptyList()

Now all findAllById calls with "1" as the first argument and "3" as the last argument return an empty list.

You can apply restrictions with wildcard matchers using varargsAny and varargsAll:

everySuspend { repository.findAllById("1", *varargsAll { it != "2" }, "3") } returns emptyList()

repository.findAllById("1", "3", "3", "3") // returns empty list
repository.findAllById("1", "2", "3", "3") // fails - method not mocked

Wildcard vararg matchers can be used with composite matchers (e.g. logical matchers):

everySuspend { repository.findAllById(*anyVarargs()) } returns listOf(stubBook("1"))
everySuspend { repository.findAllById(*not(varargsAll { it == "2" })) } returns listOf(stubBook("2"))

repository.findAllById("2", "2", "2") // returns listOf(stubBook("1"))
repository.findAllById("1", "2", "2") // returns listOf(stubBook("2"))

Custom matchers

The most straightforward way to define a custom matcher is by defining an extension on ArgMatchersScope:

// only for strings
fun ArgMatchersScope.regex(
regex: Regex
): String = matching(toString = { "regex($regex)" }, predicate = regex::matches)

// for any type
inline fun <reified T> ArgMatchersScope.eqAnyOf(
vararg values: T
): T = matching(
toString = { "eqAnyOf(${values.contentToString()})" },
predicate = { values.contains(it) }
)

It is possible to use matching as anonymous matcher directly.

You can also implement ArgMatcher and pass its instance as an argument to ArgMatchersScope.matches method.

Arguments capturing

Arguments capturing allows accessing arguments passed to mocks:

val slot = Capture.slot<String>() // stores only the latest value
everySuspend { repository.findById(capture(slot)) } returns stubBook()

repository.findById("1")

println(slot.get()) // prints "1"

By default capture matches any argument. You can change it by providing a different matcher:

val slot = Capture.slot<String>()
everySuspend { repository.findById(capture(slot, neq("1"))) } returns stubBook()

repository.findById("2")

println(slot.get()) // prints "2"

repository.findById("1") // fails - no answer provided for arg "1"

Argument capture occurs only if given definition is actually used to provide an answer for a call:


val container = Capture.container<String>() // stores multiple values

everySuspend { repository.findByName(query = any(), limit = any()) } returns listOf(stubBook())
everySuspend { repository.findByName(query = capture(container), limit = eq(10)) } returns listOf(stubBook())
everySuspend { repository.findByName(query = eq("Book 3"), limit = any()) } returns listOf(stubBook())

repository.findByName(query = "Book 1", limit = 10) // `query` arg is captured
repository.findByName(query = "Book 2", limit = 20) // `limit` parameter does not match - argument is not captured
repository.findByName(query = "Book 3", limit = 10) // answer defined later is selected here - argument is not captured

println(container.values) // prints ["Book 1"]

Argument capturing works with vararg matchers (including wildcard matchers):

val slot = Capture.slot<Array<String>>()

everySuspend { repository.findAllById(*capture(slot, anyVarargs())) } returns listOf(stubBook("1"))

repository.findAllById("1", "2", "3")
// slot contains arrayOf("1", "2", "3")

For positional vararg matchers syntax is exactly the same as for regular matchers.