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

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 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.getById(id = any()) } sequentially {
returns(stubBook("1"))
calls { stubBook("2") }
throws(IllegalStateException())
}
repository.getById("1") // returns stubBook("1")
repository.getById("2") // returns stubBook("2")
runCatching { repository.getById("3") } // throws IllegalStateException
repository.getById("4") // fails - no more answers

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

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

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

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

You can use sequentiallyReturns and sequentiallyThrows as a shorthand:

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

You can nest sequentially calls:

everySuspend { repository.getById(id = any()) } sequentially {
returns(stubBook("1"))
sequentially {
returns(stubBook("2"))
returns(stubBook("3"))
}
returns(stubBook("4"))
}
repository.getById("1") // returns stubBook("1")
repository.getById("2") // returns stubBook("2")
repository.getById("3") // returns stubBook("3")
repository.getById("4") // returns stubBook("4")
repository.getById("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")
}

Indirect supertype calls

Under the hood, original performs call to super method from mocked type. In Kotlin source code, it is not allowed to call super method of indirect supertype. However, this kind of call is possible to be generated on the compiler plugin level. It's important to note that indirect super calls for Java types (including kotlin.collections.List) and for super methods from interfaces compiled to Java defaults are not allowed. This restriction exists because such calls are validated in the JVM bytecode and result in runtime errors.

Indirect super calls feature has to be explicitly allowed in Gradle files:

mokkery {
allowIndirectSuperCalls.set(true)
}

Calling super methods from indirect supertypes is similar to calling original methods:

everySuspend { repository.findById(any()) } calls superOf<BaseRepository>()

repository.findById("2") // this calls super method implementation from BaseRepository with "2"

You can pass different arguments to super call:

everySuspend { repository.findById(any()) } calls superWith<BaseRepository>("3")

repository.findById("2") // this calls super method implementation from BaseRepository with "3"

Custom answer

To provide custom answer implement Answer:

object RandomIntAnswer : Answer<Int> {

override fun call(scope: FunctionScope) = Random.nextInt()
}

Answer that implements only call works for both regular functions and suspending functions.

everySuspend { repository.countAllBooks() } answers RandomIntAnswer

You can provide convenient extension for your custom answer:

fun AnsweringScope<Int>.returnsRandomInt() = answers(RandomIntAnswer)

// ...

everySuspend { repository.countAllBooks() }.returnsRandomInt()

If your answer needs suspension, it is recommended to implement Answer.Suspending:

data class DelayedConstAnswer<T>(
val value: T,
) : Answer.Suspending<T> {

override suspend fun callSuspend(scope: FunctionScope): T {
delay(1_000)
return value
}
}

For suspending answers it's highly recommended to introduce convenience extension. It's able to indicate that given answer only supports suspending functions:

infix fun <T> SuspendAnsweringScope<T>.returnsAfterDelay(value: T) = answers(DelayedConstAnswer(value))
// ...
everySuspend { repository.countAllBooks() } returnsAfterDelay 1

If you want to restrict answer only for regular functions, use BlockingAnsweringScope<T>.