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.

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.

  • Boolean values are set to false.

  • Numeric values are set to 0.

  • String values are set to empty "".

  • Other non-nullable non-primitive values are faked.

By using a data class, you can easily tweak your fakes according to your needs:

val user = fake<User>().copy(id = 42)

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 Unit do nothing.

  • Functions returning a value return a fake of their return type (lastUser() above returns a faked User).

  • Properties hold a fake of their type (cacheName above is ""), and a var keeps whatever it is later assigned.

  • 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.

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.

A type that (transitively) contains a non-nullable value of itself — interface Node { val parent: Node } — cannot be faked: faking it would never terminate. Make the property nullable, 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.

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