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.