Skip to main content

Answers

Essentials

To define a const answer, use returns:

everySuspend { repository.findById(id = any()) } returns Book()

To throw an exception, use throws:

everySuspend { repository.findById(id = any()) } throws IllegalArgumentException()

To provide more complex answer, use calls:

  • For regular functions:
every { repository.findAllByTitle(any()) } calls { (title: String) -> flowOf(Book(title = title)) }
  • For suspend functions:
everySuspend { repository.findById(id = any()) } calls { (id: String) ->
delay(1_000) // suspension is allowed here!
stubBook()
}

The calls lambda provides CallArgs instance as it and one of two CallDefinitionScope variants: BlockingCallDefinitionScope for every or SuspendCallDefinitionScope for everySuspend.

Utilities

To return specific arg, use returnsArgAt:

everySuspend { repository.save(book = any()) } returnsArgAt 0

repository.save(Book()) // returns Book that was passed as an argument

If you need a more complex answer for functions that return Result, use callsCatching :

everySuspend { repository.findByIdAsResult(id = "1") } callsCatching { (id: String) ->
if (id == "1") error("Not found!")
Book(id = id)
}
repository.findByIdAsResult("1") // returns failed Result
repository.findByIdAsResult("2") // returns successful Result

To return Result.success from functions that return Result, use returnsSuccess:

everySuspend { repository.findByIdAsResult(id = "1") } returnsSuccess Book(id = "1")

repository.findByIdAsResult("1") // returns successful Result with given Book

To return Result.failure from functions that return Result, use returnsFailure:

everySuspend { repository.findByIdAsResult(id = "1") } returnsFailure ElementNotFoundException(id = "1")

repository.findByIdAsResult("1") // returns failed Result with given exception

To return a value provided each time by a function, use returnsBy:

private var book = Book(...)

// ...

everySuspend { repository.findById(any()) } returnsBy ::book

repository.findById("1") // returns Book provided by `book` property
book = book.copy(title = "New title")
repository.findById("1") // returns Book provided by `book` property with changed title
tip

For functions returning a Result, you can use returnsSuccessBy or returnsFailureBy.

To throw an exception provided each time by a function, use throwsBy:

everySuspend { repository.findById(any()) } throwsBy ::IllegalStateException

runCatching { repository.findById("1") } // throws newly created IllegalStateException
runCatching { repository.findById("1") } // throws newly created IllegalStateException

To throw IllegalStateException with specific message (just like kotlin.error), use throwsErrorWith:

everySuspend { repository.findById(id = any()) } throwsErrorWith "Failed!"

Sequence of answers

You can define a sequence of answers using sequentially:

everySuspend { repository.findById(id = any()) } sequentially {
returns(stubBook("1"))
calls { stubBook("2") }
throws(IllegalStateException())
}
repository.findById("1") // returns stubBook("1")
repository.findById("2") // returns stubBook("2")
runCatching { repository.findById("3") } // throws IllegalStateException
repository.findById("4") // fails - no more answers

At the end of sequentially block you can repeat a sequence of answers with repeat:

everySuspend { repository.findById(id = any()) } sequentially {
returns(stubBook("1"))
repeat { returns(stubBook("2")) }
}
repository.findById("1") // returns stubBook("1")
repository.findById("2") // returns stubBook("2")
repository.findById("3") // returns stubBook("2")
repository.findById("4") // returns stubBook("2")

You can use sequentiallyRepeat as a shorthand if you want to define repeating sequence:

everySuspend { repository.findById(id = any()) } sequentiallyRepeat {
returns(stubBook("1"))
returns(stubBook("2"))
}
repository.findById("1") // returns stubBook("1")
repository.findById("2") // returns stubBook("2")
repository.findById("3") // returns stubBook("1")
repository.findById("4") // returns stubBook("2")

You can use sequentiallyReturns and sequentiallyThrows as a shorthand:

everySuspend { repository.findById(id = any()) } sequentiallyReturns listOf(stubBook("1"), stubBook("2"))
repository.findById("1") // returns stubBook("1")
repository.findById("2") // returns stubBook("2")
repository.findById("3") // fails - no more answers

You can nest sequentially calls:

everySuspend { repository.findById(id = any()) } sequentially {
returns(stubBook("1"))
sequentially {
returns(stubBook("2"))
returns(stubBook("3"))
}
returns(stubBook("4"))
}
repository.findById("1") // returns stubBook("1")
repository.findById("2") // returns stubBook("2")
repository.findById("3") // returns stubBook("3")
repository.findById("4") // returns stubBook("4")
repository.findById("5") // fails - no more answers

Calling original implementation

The most straightforward way to call original method is to use calls overload:

everySuspend { repository.findById(any()) } calls original

repository.findById("2") // this calls original method implementation with "2"

You can pass different arguments to original call:

everySuspend { repository.findById(any()) } calls originalWith("3")

repository.findById("2") // this calls original method implementation with "3"
info

If mocked type is an interface, the default implementation is called.

Resolving ambiguity

While using multiple types mocking usage of original might not be possible, because there are multiple super calls available for single method. Instead of original use superOf<T> with supertype specified.

val mock = mockMany<A, B> {
every { t1.sharedMethod(any()) } calls superOf<A>()
}
mock.t1.sharedMethod(2) // this calls method implementation from A with 2

You can pass different arguments to super call:

val mock = mockMany<A, B> {
every { t1.sharedMethod(any()) } calls superWith<A>(3)
}
mock.t1.sharedMethod(2) // this calls method implementation from A with 3

Super calls API in calls

All of those features are accessible from calls scope:

everySuspend { repository.findById(any()) } calls {
callOriginal()
callOriginalWith("3")
callSuper(BaseRepository::class)
callSuperWith(BaseRepository::class, "3")
}

Calling spied implementation

When working with spies, you may want to call the real implementation while adding some extra behavior. This can be done using callSpied from calls:

everySuspend { repository.findById(any()) } calls {
// ...
callSpied()
}

If you need to override the arguments passed to the spied method, use callSpiedWith:

everySuspend { repository.findById(any()) } calls {
// ...
callSpiedWith("3")
}

Extending answers API

The answers API provides a way to define custom behavior for mocked method calls in Mokkery. It is built around two core interfaces:

@DelicateMokkeryApi
public interface Answer<out T> {

public fun call(scope: MokkeryBlockingCallScope): T

public suspend fun call(scope: MokkerySuspendCallScope): T
}

// ...

public interface AnsweringScope<T> {

@DelicateMokkeryApi
public infix fun answers(answer: Answer<T>)
}

Core concepts

The Answer interface represents custom behavior for mocked calls. It has two call overloads:

  • call(MokkeryBlockingCallScope) – used for regular (blocking) functions.
  • call(MokkerySuspendCallScope) – used for suspend functions.

The AnsweringScope interface provides the answers method, which registers an Answer for a particular call.

Some custom answers may work with both regular and suspend methods, while others support only one type. However, AnsweringScope itself does not enforce any restrictions - you can technically register any Answer for any call.

This flexibility can lead to misuse, such as registering a suspend-only answer for a blocking call. For this reason, both AnsweringScope.answers and the Answer interface are marked as delicate APIs.

Restricting usage

To provide compile-time safety, Mokkery defines two subtypes of AnsweringScope:

public interface SuspendAnsweringScope<T> : AnsweringScope<T>

public interface BlockingAnsweringScope<T> : AnsweringScope<T>

These are marker interfaces - they don’t add extra methods but indicate whether the context is for a regular method (BlockingAnsweringScope), or a suspend method (SuspendAnsweringScope).

Mokkery uses them as follows:

  • every returns a BlockingAnsweringScope
  • everySuspend returns a SuspendAnsweringScope

Custom Answer implementations should not be used directly. Instead, expose them through convenience extensions on the appropriate scope type.

Example: a suspend-only answer that delays before returning a value:

infix fun SuspendAnsweringScope<T>.returnsDelayed(value: T) {
answers(ReturnsDelayedAnswer(value))
}

// you can optionally mark this class as private
private class ReturnsDelayedAnswer<T>(private val value: T) : Answer<T> {
public fun call(scope: MokkeryBlockingCallScope): T = error("Not supported")

public suspend fun call(scope: MokkerySuspendCallScope): T {
delay(1_000)
return value
}
}

// Usage:
everySuspend { repository.findById(any()) } returnsDelayed stubBook()
// ✅ compiles - suspend context

every { repository.findAll() } returnsDelayed flowOf(stubBook())
// ❌ does NOT compile - regular context

Some answers are valid for both regular and suspend methods. For consistency, you should still declare an extension function, but this time use AnsweringScope as the receiver:

infix fun AnsweringScope<T>.returns(value: T) {
answers(ReturnsAnswer(value))
}

private class ReturnsAnswer<T>(private val value: T) : Answer<T> {
public fun call(scope: MokkeryBlockingCallScope): T = value
public suspend fun call(scope: MokkerySuspendCallScope): T = value
}

// Usage:
everySuspend { repository.findById(any()) } returns stubBook() // suspend
every { repository.findAll() } returns flowOf(stubBook()) // regular
// ✅ both compile

Using existing answers

If your custom answer can be expressed using other built-in answers, you don’t need to create a new Answer implementation. Instead, declare an extension using the existing calls function:

fun <T> SuspendAnsweringScope<T>.returnsDelayed(value: T) {
calls {
delay(1_000)
value
}
}

This is simpler and avoids extra boilerplate.

Answer description

The Answer interface lets you implement a description() method, which provides a human-readable explanation of the answer. This is especially useful for debugging, e.g., with printMokkeryDebug

The description should resemble the code usage:

infix fun SuspendAnsweringScope<T>.returnsDelayed(value: T) {
answers(ReturnsDelayedAnswer(value))
}

// ...
private class ReturnsDelayedAnswer<T>(private val value: T) : Answer<T> {
// ...
override fun description() = "returnsDelayed $value"
}

Answer implementation helpers

When implementing custom answers, these helper interfaces can useful:

  • Answer.Suspending - For suspend-only answers. Provides a default blocking implementation that throws an exception.
  • Answer.Blocking - For blocking-only answers. Provides a default suspend implementation that throws an exception.
  • Answer.Unified - Adds a third call overload with MokkeryCallScope. The other two overloads delegate to this method, so you only need to implement one function. Useful when the same logic applies to both blocking and suspend contexts.