Limitations
All versions
Those limitations affect all Mokkery versions with some variations between the versions.
Supported types
✅ In general, Mokkery is able to mock a type that is fully overridable. That includes:
- 🧩 Interfaces
- ➡️ Functional types
- 🏛️ Final classes with all-open plugin
- 🏗️ Abstract/open classes with public constructor and all overridable public members
- 🚨 Support for mocking classes with parameterized constructors has evolved over time. For more details, please refer to this section.
- 💡It's possible to allow final or inline members
❌ Things that Mokkery is unable to mock:
- ➡️ Functions (extensions included)
- 🏛️ Final classes that are already compiled (it's not possible to apply
all-openplugin) - 🔢 Primitives
- 🔐 Sealed types
- 🗽 Objects
Calling mock & spy
Type passed to spy and mock must be directly specified. Following code is illegal:
inline fun <reified T : Any> myMock() = mock<T>()
However, it is not completely forbidden to use generic parameters. Following code is allowed:
fun <T : Any> myListMock() = mock<List<T>>()
Calling every & verify
To ensure that every and verify work as expected, compiler plugin transforms the code inside their blocks. This
transformation currently restricts those blocks from extracting their parts into separate functions. It also dictates that block
parameter must always be a lambda expression (not function reference nor lambda assigned to a variable). Following code is illegal:
@Test
fun test() {
// ...
verify {
foo()
}
}
private fun MokkeryMatcherScope.foo() {
repository.findAll()
}
However, it is perfectly fine to extract whole verify or every call to separate function:
@Test
fun test() {
// ...
foo()
}
private fun foo() {
verify {
repository.findAll()
}
}
Mocking classes with parameterized constructors
Since Mokkery 2.3.0, a public no-argument constructor is no longer required for a type to be mockable.
Earlier implementations had an additional, implicit limitation: complex constructor arguments could not be accessed.
This was because, on most platforms, Mokkery simply passed null to satisfy constructor parameters - even when the type was non-nullable.
However, starting with version 2.2.20 (WASM) and 2.3.0 (Native), this approach was no longer viable due to stricter runtime checks.
Mokkery 3.1.0 introduces a more robust and correct solution. It allows access to constructor parameters by attempting to supply actual instances of the required types. This approach has one limitation - Mokkery must be able to provide an instance of given type.
Mokkery uses the following strategies to supply constructor arguments:
nullfor nullable types0for all numeric typesfalseforBoolean'\u0000'forCharAny()forAny- A
kotlin.reflect.KClassinstance matching the required type (e.g.Int::classforKClass<Int>) - The
Unitobject forUnit - Empty arrays for all array types
- Empty collections for
Iterable,Collection,List,Set,Map, and their mutable counterparts - The first declared entry for enum types
- Lambdas that return values resolved using the same strategies
- Generated stub implementations for interfaces
- Instantiating concrete classes using available constructors, following the same rules as above.
By default, this works only for inline classes,
Throwablesubclasses, and classes fromkotlin.collections,kotlin.sequences, andkotlin.ranges. For other types, explicit permission is required because invoking constructors may execute unintended code during tests. Public and internal constructors are supported, with default constructors preferred. Enable this behavior in your Gradle build file using themokkery.stubs.allowConcreteClassInstantiationflag. - Generating stub implementations for classes.
This requires explicit permission for the same reason as concrete classes instantiation.
Enable this behavior in your Gradle build file using
mokkery.stubs.allowClassInheritanceflag.
Mokkery selects the first applicable strategy based on the order listed above.
Before Mokkery 3
Given limitations affect only Mokkery 1 and 2 and they are not present in Mokkery 3.
Value classes (WASM)
Mokkery supports mocking methods involving value classes, whether they serve as return types or parameters.
However, for WASM, this support is restricted only to value classes originating from the standard library
(kotlin.Result and kotlin.time.Duration).
Any other value class requires registering AutofillProvider that returns some empty value:
AutofillProvider.forInternals.types.register { ValueClass(null) }
Using matchers
All matchers
The biggest limitation is that you must not assign matchers to variables. Following code is illegal:
everySuspend {
val matcher = any<Int>()
reporitory.findById(matcher)
} returns Book(...)
Composite matchers
When using composite matchers (whenever matcher accepts other matcher like logical matchers), you cannot use literals. Code below is illegal:
everySuspend { repository.findById(or("1", "2")) } returns Book(...)
You must use eq matcher explicitly:
everySuspend { repository.findById(or(eq("1"), eq("2"))) } returns Book(...)
Varargs matchers
If you pass varargs as array, it might sometimes lead to ambiguity. Calls presented below are prohibited:
everySuspend { repository.findAllById(ids = arrayOf("1", *anyVarargs(), "3")) } returns emptyList()
everySuspend { repository.findAllById(ids = arrayOf("1", any())) } returns emptyList()
While passing varargs as arrays make sure that you don't mix matchers with literals. Calls presented below are allowed:
everySuspend { repository.findAllById(ids = arrayOf(eq("1"), *anyVarargs(), eq("3"))) } returns emptyList()
everySuspend { repository.findAllById(ids = arrayOf(eq("1"), any())) } returns emptyList()