Skip to main content

Factories

mock and spy require the type to be known at compile time - it is a type argument of the call. That's perfectly fine for regular test code, but it makes it hard to write utilities that decide what to mock at runtime, e.g. a small container that provides a mock for each dependency of a tested component.

Factories address that. A factory is created for a fixed set of classes, but it creates instances from a KType given at runtime:

import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import kotlin.reflect.typeOf

val factory = mockFactoryOf(Foo::class, Bar::class)

val foo = factory.create(typeOf<Foo>()) // returns Any - a mock of Foo

Each argument of mockFactoryOf must be a class literal (e.g. Foo::class) and it has to satisfy the same requirements as mock:

  • Each type must be supported.
  • Functional types are not supported on JS.
info

The set of types that a factory can create is still fixed at compile time - the plugin generates an implementation for each class literal passed to mockFactoryOf. Factories don't make Mokkery reflective, they only move the type selection to runtime, so more dynamic patterns become possible without boilerplate.

Creating mocks

MockFactory exposes createOrNull that returns null for unsupported types:

import dev.mokkery.factory.mockFactoryOf
import kotlin.reflect.typeOf

val factory = mockFactoryOf(Foo::class)

factory.createOrNull(typeOf<Foo>()) // returns a mock of Foo
factory.createOrNull(typeOf<Baz>()) // returns null - Baz is not registered

There is also create that fails instead of returning null. Both have a reified overload for cases where the type happens to be known statically:

import dev.mokkery.factory.create
import dev.mokkery.factory.createOrNull
import kotlin.reflect.typeOf

val untyped: Any = factory.create(typeOf<Foo>())
val foo: Foo = factory.create<Foo>()
val fooOrNull: Foo? = factory.createOrNull<Foo>()

All of them accept a configuration block, just like mock:

import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.mockMode

val foo = factory.create<Foo> {
mockMode = MockMode.autofill
every { call() } returns 1
}

A type matches a factory when its classifier is one of the registered classes. Type arguments are taken from the provided KType, so a single registration covers all parameterizations:

import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf

val factory = mockFactoryOf(List::class)

factory.create<List<String>>()
factory.create<List<Int>>()

Here is an example of a utility that with mock alone would require registering each type by hand:

import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import kotlin.reflect.KType
import kotlin.reflect.typeOf

interface Dependencies {

fun get(type: KType): Any
}

class MockDependencies : Dependencies {

private val factory = mockFactoryOf(Foo::class, Bar::class)
private val instances = mutableMapOf<KType, Any>()

override fun get(type: KType): Any = instances.getOrPut(type) { factory.create(type) }
}

val dependencies: Dependencies = MockDependencies()

val foo = dependencies.get(typeOf<Foo>())
val bar = dependencies.get(typeOf<Bar>())

Default mock mode

mockFactoryOf accepts a block that configures the factory itself. Mocks derive their configuration from the factory they come from, so defaultMockMode sets the mock mode for the mocks it creates:

import dev.mokkery.MockMode
import dev.mokkery.factory.defaultMockMode
import dev.mokkery.factory.mockFactoryOf

val factory = mockFactoryOf(Foo::class, Bar::class) {
defaultMockMode = MockMode.autoUnit
}

It is still possible to override it for a single instance with mockMode in the create block.

Presets

preset registers a configuration block that is applied to every instance of a given type created by the factory. It is a good place for defaults that all instances of a type should share:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import dev.mokkery.presets.preset

val factory = mockFactoryOf(Foo::class, Bar::class) {
preset<Foo> {
every { call() } returns 1
}
}

factory.create<Foo>().call() // 1

A preset is applied before the block given to create, so a single instance can still override it:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create

val foo = factory.create<Foo> {
every { call() } returns 2 // wins over the preset
}

Presets are matched by the exact type - a preset registered for a supertype of a created mock is not applied. Type arguments are matched by their classes, so each parameterization can be configured separately:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import dev.mokkery.presets.preset

val factory = mockFactoryOf(List::class) {
preset<List<String>> { every { size } returns 1 }
preset<List<Int>> { every { size } returns 2 }
}

factory.create<List<String>>().size // 1
factory.create<List<Int>>().size // 2

A star projection matches any type argument:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import dev.mokkery.presets.preset

val factory = mockFactoryOf(List::class) {
preset<List<*>> { every { size } returns 1 }
}

factory.create<List<String>>().size // 1
factory.create<List<Int>>().size // 1

Presets are cumulative - registering a preset for a type that already has one does not replace it. All matching presets are applied, from the least to the most specific match and in the registration order, so the most specific and the most recently registered one takes precedence:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.factory.mockFactoryOf
import dev.mokkery.presets.preset

val factory = mockFactoryOf(List::class) {
preset<List<*>> {
every { size } returns 1
every { isEmpty() } returns false
}
preset<List<String>> { every { size } returns 2 }
}

val strings = factory.create<List<String>>()

strings.size // 2 - the exact preset is applied after the star projected one
strings.isEmpty() // false - still comes from the star projected preset
info

Registering a preset for a type that the factory cannot create has no effect.

Combining factories

Factories can be combined with +. The resulting factory creates instances of all types from both sides, preserving their configuration:

import dev.mokkery.MockMode
import dev.mokkery.factory.create
import dev.mokkery.factory.defaultMockMode
import dev.mokkery.factory.mockFactoryOf
import dev.mokkery.factory.plus

val factory = mockFactoryOf(Foo::class) { defaultMockMode = MockMode.autoUnit } +
mockFactoryOf(Bar::class) { defaultMockMode = MockMode.autofill }

val foo = factory.create<Foo>() // autoUnit - comes from the left factory
val bar = factory.create<Bar>() // autofill - comes from the right factory

Spy factories

spyFactoryOf works exactly like mockFactoryOf, but it creates spies, so an object to spy on is required:

import dev.mokkery.factory.create
import dev.mokkery.factory.createOrNull
import dev.mokkery.factory.spyFactoryOf
import kotlin.reflect.typeOf

val factory = spyFactoryOf(Foo::class, Bar::class)

val foo = factory.create(typeOf<Foo>(), FooImpl())
val bar = factory.create<Bar>(BarImpl())
val bazOrNull = factory.createOrNull<Baz>(BazImpl()) // null - Baz is not registered

The spied object must be an instance of the given type.

defaultMockMode does not apply to spies, but the remaining factory configuration works the same way, including presets:

import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.factory.create
import dev.mokkery.factory.spyFactoryOf
import dev.mokkery.presets.preset

val factory = spyFactoryOf(Foo::class) {
preset<Foo> { every { call() } returns 1 }
}

factory.create<Foo>(FooImpl()).call() // 1 - the spied object is not called