Faking types
A fake is an inert instance: a value with no behaviour, whose data is zero-valued.
Data classes are ideal candidates for faking: they are constructed with a faked value for each of their properties. Interfaces and abstract classes are faked as well, by generating an implementation of them.
Requesting generation
You can declare that a class or function needs a specific faked data by using the @UsesFakes annotation.
@UsesFakes(User::class)
class MyTests
// and
@UsesFakes(User::class)
fun testUser() {}
Once a type appears in @UsesFakes, the processor will generate a fake function for it.
|
A type your code never names, but that MocKMP still needs — e.g. a constructor parameter of a type
you did request — is faked internally too, so whatever needs it keeps working, but This is deliberate. If you called |
Instantiating
Once a class has been faked, you can get a new instance by the fake function:
@UsesFakes(User::class)
class MyTests {
val user = fake<User>()
}
Here are the rules the processor uses to generate fakes:
-
Nullable values are always
null. -
Booleanvalues are set tofalse. -
Numeric values are set to
0. -
Stringvalues are set to empty"". -
Nothingvalues throw anUnsupportedOperationExceptionwhen reached, since none can exist. -
List,Set,Mapand their concrete variants (ArrayList,ArrayDeque,HashSet,LinkedHashSet,HashMap,LinkedHashMap) are faked empty, andArrayis faked as an empty array. -
When
kotlinx.coroutinesis on the classpath, its types are faked as real, inert instances rather than a generated implementation:FlowasemptyFlow();SharedFlow/MutableSharedFlowasMutableSharedFlow();StateFlow/MutableStateFlowasMutableStateFlow(<faked value>)— the wrapped value is faked exactly like any other property, soStateFlow<User>holds a fakedUserandStateFlow<User?>holdsnull;Channel/ReceiveChannel/SendChannelasChannel();Job/CompletableJobasJob();Deferred/CompletableDeferredasCompletableDeferred();CoroutineScopeasCoroutineScope(EmptyCoroutineContext);MutexasMutex();SemaphoreasSemaphore(1).kotlin.coroutines.CoroutineContextis always faked asEmptyCoroutineContext, coroutines dependency or not, since it’s part of the Kotlin standard library. -
Other non-nullable non-primitive values are faked.
|
By using a
|
Faking interfaces and abstract classes
A type that cannot be constructed can still be implemented, so MocKMP fakes an interface (or an abstract class) by generating a class that implements it:
interface UserRepository {
val cacheName: String
fun record(user: User)
fun lastUser(): User
fun describe(): String = "$cacheName cache" (1)
}
@UsesFakes(UserRepository::class)
class MyTests {
val repository = fake<UserRepository>()
}
| 1 | Not abstract, so it is not overridden: it runs, over the faked members it reads. |
Its members follow the same rules as everything else on this page:
-
Functions returning
Unitdo nothing. -
Functions returning a value return a fake of their return type (
lastUser()above returns a fakedUser). -
Properties hold a fake of their type (
cacheNameabove is""), and avarkeeps whatever it is later assigned. -
A property holding another fake is generated
by LazyFake { }, so — unlike a function’s return value, which was already only built when the function is called — it is not built until that property is first read. Reading it again reuses the same value. -
A member typed
Nothingthrows when reached instead: no value of that type exists, so a fake of an interface withval impossible: Nothingorfun fail(): Nothingstill constructs — it is only that member that fails, if something ever reads or calls it. -
Only abstract members are overridden: a default implementation is left to run over them.
-
An abstract class is constructed with faked arguments, exactly as a concrete class would be.
The generated implementation class itself is private to the file it is generated in — fake<T>() (or
an injected @Fake property) is the only way to obtain an instance of it.
Prefer @Mock over @Fake for a collaborator whose calls the test needs to configure or
verify: a fake records nothing and cannot be given behaviour.
A fake is for the collaborators that merely need to exist.
|
A function whose return type is one of its own type parameters returns the parameter that holds a
value of that same type, if it has one: fun <T> convert(value: T): T is faked as
convert(value) = value.
If it has none (fun <T> get(): T), it is the one member that cannot be faked — no single value can
satisfy every T a caller may ask for — and it throws when called; the rest of the fake is
unaffected.
Deferring a property’s value until it is read (see above) is what makes a self-referential interface or abstract class fakeable at all:
interface Node {
val name: String
val parent: Node
}
fake<Node>() builds only the root; each further .parent is built the moment it is read, so
node.parent.parent.parent works even though nothing about Node bounds how deep it can go.
A constructor parameter is resolved eagerly — the instance cannot exist without a value
for it — so a type that (transitively) requires a non-nullable value of itself as a constructor
parameter, e.g. class Node(val parent: Node) or an abstract class of the same shape, still cannot
be faked: faking it would never terminate.
Make the parameter nullable, turn it into an interface property as above, or provide the fake
yourself.
|
Providing fake instances
Classes that do not have a public constructor cannot be automatically faked.
For these types, you need to provide your custom fake provider with @FakeProvider:
@FakeProvider
fun provideFakeInstant() = Instant.fromEpochSeconds(0)
| There can be only one provider per type, and it needs to be a top-level function. |
A @FakeProvider also overrides how any of the types above — including collections and
coroutines types — is faked, for that exact type. A provider for List<String> only replaces
emptyList() where a List<String> is faked; a List<Int> elsewhere is unaffected.
|
Generics
You can fake a Star-projected generic type with @UsesFakes:
data class NullGenData<T>(val content: T)
data class NonNullGenData<T : Any>(val content: T)
@Test
@UsesFakes(NullGenData::class, NonNullGenData::class)
fun testGenericFake() {
val nullData = fake<NullGenData<*>>()
assertNull(nullData.content) (1)
val nonNullData = fake<NonNullGenData<*>>()
assertNotNull(nonNullData.content) (2)
}
| 1 | A star projection carries no type argument, so each type parameter is faked as its bound. T is unbounded here, which means Any?: the fake is null. |
| 2 | T’s bound is `Any, so the fake is an Any instance. |
However, if you need a specific generic type to fake, you need to declare it in an injected class, even if you are never going to use that class.
data class GenData<T>(val content: T)
class GenFakes {
@Fake lateinit var longData: GenData<String>
}
@Test
fun testDataOfLong() {
val data = fake<GenData<String>>()
assertEquals("", data.content)
}