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
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"
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 aBlockingAnsweringScope
everySuspend
returns aSuspendAnsweringScope
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.