Most
stubs will use returns
to always return the same value when the corresponding method is called. Sometimes, the method you’re trying to mock may be more complicated and require a more advanced stub.
If you want to change the result of a mocked method depending on its arguments, run additional code whenever a method is called, or just print out “Hello world” when a mock is used, answers
is the tool you’ll want to use.
answers
takes the place of returns
after an every
call. When using returns
, it should be followed by a value to return. When using answers
, it should be followed by a lambda function that is run when the mocked method is called.
import io.mockk.every
import io.mockk.mockk
val navigator = mockk<Navigator>()
every { navigator.currentLocation } returns "Home"
every { navigator.currentLocation } answers { "Home" }
Side effects #
A function has “side effects” if it does something other than just returning a value, such as logging or mutating some outside state. answers
lets you model side effects by putting additional statements inside its lambda function.
every { navigator.currentLocation } answers {
println("Hello world!")
"Work"
}
// prints "Hello world!"
val location = navigator.currentLocation
// prints "Work"
println(location)
Answer scope #
Inside of the answers
lambda function, you can access information about the mocked method and how it was called. You can then use this to adjust the resulting answer.
every { navigator.navigateTo(any()) } answers {
val destination = firstArg<String>()
throw IllegalStateException("Can't reach $destination")
}
These values are properties on the
MockKAnswerScope
class. The scope is passed as a
receiver object, allowing variables to be called from the implicit this
scope.
Individual arguments #
Single arugments can be obtained using firstArg()
, secondArg()
, thirdArg()
, and lastArg()
. Other arguments can be obtained with arg(n)
, where n
is the index of the argument. For example, arg(3)
would return the fourth argument.
Arguments do not have static type checking. Instead, the type is casted automatically using generic types.
every { calculator.add(any<Int>(), any<Int>()) } answers {
// tries to cast the second argument to a string
println(secondArg<String>())
0
}
The above code will compile, but when the test is run a ClassCastException
will be thrown because you cannot cast an Int
to a String
.
All arguments #
The entire list of arguments can be obtained using args
. args
has the type List<Any?>
, so you will need to manually cast values in the list if you want to work with them.
every { calculator.add(any<Int>(), any<Int>()) } answers {
val numbers = args as List<Int>
numbers.sum()
}
// prints "4"
println(calculator.add(2, 2))
If you only need to know the length of args
, aka the number of arguments, you can use nArgs
.
TODO call
, invocation
, matcher
, self
, method
, captured
, lambda
, coroutine
, nothing
, fieldValue
, fieldValueAny
, value
, valueAny
.