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.

Buy Me a Coffee at ko-fi.com