Skip to main content

Matchers

Regular matchers

Matchers are quite straightforward to use. Instead of literal argument, you have to pass a matcher. You can use named parameters, change their order and extract them to variables!

Full list of matchers with documentation is available here.

Logical matchers

Logical matchers allows combining regular matchers into logical expressions.

every { mock.getAt(or(1, not(2))) } returns 10

Full list of logical matchers is available here.

Vararg matchers

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

everySuspend { mock.insertAll(1, any(), 3) } returns 0

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 any matcher that accepts array with spread (*) operator. For example you can use any() to accept any arguments in between.

everySuspend { mock.insertAll(1, *any(), 3) } returns 0

Now all insertAll calls with 1 as the first argument and 3 as the last argument return 0.

Mokkery provides several array-specific matchers, such as containsAllElements (with numeric variants like containsAllInts, containsAllFloats, etc.) that also can be used with spread operator:

everySuspend { mock.insertAll(1, *containsAllInts { it != 2 }, 3) } returns 0

mock.insertAll(1, 3, 3, 3) // returns 0
mock.insertAll(1, 2, 3, 3) // fails - method not mocked

You can provide custom matcher for spread operator:

everySuspend { mock.insertAll(1, *matches { it.size == 2 }, 3) } returns 0

mock.insertAll(1, 2, 2, 3) // returns 0
mock.insertAll(1, 5, 5, 3) // returns 0
mock.insertAll(1, 2, 2, 2, 3) // fails - method not mocked
mock.insertAll(1, 2, 3) // fails - method not mocked

Spread operator can be used with composite matchers (e.g. logical matchers):

everySuspend { mock.insertAll(*any()) } returns 1
everySuspend { mock.insertAll(*not(containsAllElements { it == "2" })) } returns 2

mock.insertAll(2, 2, 2) // returns 1
mock.insertAll(1, 2, 2) // returns 2

Custom matchers

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

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

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

It is possible to use matches as anonymous matcher directly.

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

Arguments capturing

Arguments capturing allows accessing arguments passed to mocks:

val slot = Capture.slot<Int>() // stores only the latest value
every { mock.getAt(capture(slot)) } returns 1

mock.getAt(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<Int>()
every { mock.getAt(capture(slot, not(1))) } returns 0

mock.getAt(2)

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

mock.getAt(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<Int>() // stores multiple values

every { mock.getAtOrDefault(index = any(), default = any()) } returns 0
every { mock.getAtOrDefault(index = capture(container), default = 10) } returns 0
every { mock.getAtOrDefault(index = 3, default = any()) } returns 0

mock.getAtOrDefault(index = 1, default = 10) // `index` arg is captured
mock.getAtOrDefault(index = 2, default = 20) // `default` parameter does not match - argument is not captured
mock.getAtOrDefault(index = 3, default = 10) // answer defined later is selected here - argument is not captured

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

Argument capturing works with vararg matchers (including matchers with spread operator):

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

everySuspend { mock.insertAll(*capture(slot)) } returns 1

mock.insertAll(1, 2, 3)
// slot contains intArrayOf(1, 2, 3)

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