Skip to main content

Mocking

Let's assume we have given interface:

interface BooksRepository {

suspend fun findById(id: String): Book

suspend fun countAll(): Int

fun findAll(): Flow<Book>
}

⚠️ Please read limitations section!

Mock tracks all method calls and allows defining their answers.

To create a mock of BooksRepository use mock function:

val repository = mock<BooksRepository>()

If you call a method that has no defined behaviour, runtime exception is thrown. It's dictated by MockMode.strict.

Mock modes

Mokkery provides 4 modes for missing answers.

Strict

It is the default mode that fails on missing answers.

import dev.mokkery.MockMode.strict

val repository = mock<BookRepository>(strict)

AutoUnit

Just like strict but it does not fail on Unit returning methods.

import dev.mokkery.MockMode.autoUnit

val repository = mock<BookRepository>(autoUnit)

Autofill

Returns empty values e.g. 0 for numbers, "" for string and null for complex types.

import dev.mokkery.MockMode.autofill

val repository = mock<BookRepository>(autofill)

Original

Calls super implementation if available (default implementation for interface). Otherwise, it fails. Useful for mocking types highly dependant on default behaviour.

import dev.mokkery.MockMode.original

val repository = mock<BookRepository>(original)

Default mock mode

It is possible to change the default MockMode on the Gradle plugin level like this:

import dev.mokkery.MockMode

mokkery {
defaultMockMode.set(MockMode.autoUnit)
}

Defining answers

To define an answer for regular function use every:

every { repository.findAll() } returns flowOf(Book(...))

For suspending function use everySuspend:

everySuspend { repository.countAll() } returns 1

⚠️ Make sure that you are calling a method of BooksRepository! Mocking extension functions is not supported!

It's worth to notice that everySuspend is not suspending function, so it's possible to configure suspending functions in non-suspending context e.g. test class property.

You can move your behaviour config to mock block:

val repository = mock<BookRepository>(autoUnit) {
every { findAll() } returns flowOf(Book(...))
everySuspend { countAll() } returns 1
}

returns is quite simple and returns always the same value. If you want to discover other behaviours check answers guide!

If a method accepts parameters, you can define an answer only for specific parameters:

everySuspend { repository.findById("1") } returns Book(id = "1", ...)
everySuspend { repository.findById("2") } returns Book(id = "1", ...)

repository.findById("1") // returns Book(id = "1", ...)
repository.findById("2") // returns Book(id = "2", ...)
repository.findById("3") // error - answer not defined

To accept more broad range of parameter values use matchers:

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

repository.findById("1") // returns Book(id = "1", ...)
repository.findById("2") // returns Book(id = "1", ...)
repository.findById("3") // returns Book(id = "1", ...)

In case of a call that matches more than one answer, the later defined takes precedence:

// this answer is unreachable as the later defined matches all possible calls
everySuspend { repository.findById("1") } returns Book(id = "1", ...)
everySuspend { repository.findById(any()) } returns Book(id = "2", ...)

repository.findById("1") // returns Book(id = "2", ...)

Removing answers

To remove all defined answers use resetAnswers:

everySuspend { repository.findById("1") } returns Book(id = "1", ...)

resetAnswers(repository)

repository.findById("1") // error - answer not defined

Abstract/open class with final members

By default, it is illegal to mock open or abstract types with final (inline included) members.

You can ignore those members with following Gradle options:

mokkery {
ignoreInlineMembers.set(true) // ignores only inline members
ignoreFinalMembers.set(true) // ignores final members (inline included)
}

With given flags, Mokkery ignores illegal members, but it's still not possible to change their behaviour or track them. It's possible only for overridable methods.

Final classes

Mocking final classes that are already compiled is currently not possible. This includes any class defined in main source set, therefore this feature is not supported. However, you can "open" your final classes from main source set with all-open plugin.

You have to define an annotation:

package your.package

annotation class OpenForMokkery()

Apply all-open plugin and configure it:

plugins {
id("org.jetbrains.kotlin.plugin.allopen")
}

allOpen {
annotation("your.package.OpenForMokkery")
}

Add OpenForMokkery to final class:

@OpenForMokkery
class Foo {

fun foo() = Unit
}

Be aware that this plugin makes your types open in a production code. It might be problematic in case your artifacts being consumed by other libraries. However, it should be fine for any type of applications.