Skip to main content

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:

❌ Things that Mokkery is unable to mock:

  • ➡️ Functions (extensions included)
  • 🏛️ Final classes that are already compiled (it's not possible to apply all-open plugin)
  • 🔢 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:

  • null for nullable types
  • 0 for all numeric types
  • false for Boolean
  • '\u0000' for Char
  • Any() for Any
  • A kotlin.reflect.KClass instance matching the required type (e.g. Int::class for KClass<Int>)
  • The Unit object for Unit
  • 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, Throwable subclasses, and classes from kotlin.collections, kotlin.sequences, and kotlin.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 the mokkery.stubs.allowConcreteClassInstantiation flag.
  • 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.allowClassInheritance flag.

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()