Skip to content

Commit

Permalink
MocKMP 2.0 documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
SalomonBrys committed Nov 30, 2024
1 parent 8e23751 commit 959285c
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 173 deletions.
9 changes: 4 additions & 5 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ WARNING: MocKMP is currently NOT compatible with Kotlin 2.x.

A Kotlin/Multiplatform Kotlin Symbol Processor that generates Mocks & Fakes.

Built in collaboration with https://www.deezer.com/[Deezer].


== Installation

Expand All @@ -24,8 +22,9 @@ kotlin {
}
mockmp {
usesHelper = true
installWorkaround()
onTest {
withHelper()
}
}
----

Expand All @@ -35,7 +34,7 @@ mockmp {
[source,kotlin]
----
class MyTest : TestsWithMocks() {
override fun setUpMocks() = injectMocks(mocker) //<1>
override fun setUpMocks() = mocker.injectMocks(this)
@Mock lateinit var view: View
@Fake lateinit var model: Model
Expand Down
10 changes: 6 additions & 4 deletions doc/antora.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
name: mockmp
title: MocKMP
version: '1.17'
display_version: '1.17.0'
version: '2.0'
display_version: '2.0.0'
nav:
- modules/ROOT/nav.adoc
- modules/core/nav.adoc
- modules/migration/nav.adoc
asciidoc:
attributes:
version: '1.17.0'
ksp-version: '1.9.22-1.0.17'
version: '2.0.0'
kotlin-version: '2.0.21'
ksp-version: '2.0.21-1.0.28'
25 changes: 17 additions & 8 deletions doc/modules/ROOT/pages/getting-started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,33 @@
[source,kotlin,subs="verbatim,attributes"]
----
plugins {
kotlin("multiplatform")
id("org.kodein.mock.mockmp") version "{version}"
kotlin("multiplatform") version "{kotlin-version}"
id("com.google.devtools.ksp") version "{ksp-version}" // <1>
id("org.kodein.mock.mockmp") version "{version}" // <2>
}
kotlin {
jvmToolchain(17)
// Your Koltin/Multiplatform configuration
}

mockmp {
usesHelper = true
installWorkaround()
mockmp { // <3>
onTest {
withHelper() // or withHelper(junit5)
}
}
----
<1> Apply the KSP plugin that corresponds to the Kotlin version you are using.
<2> Apply the MocKMP plugin.
<3> Apply MocKMP to your test source-sets (must be configured *after* declaring `kotlin` targets).

2. Create a test class that declares injected mocks and fakes:
+
--
[source,kotlin]
----
class MyTest : TestsWithMocks() {
override fun setUpMocks() = injectMocks(mocker) //<1>
class MyTest : TestsWithMocks() { // <1>
override fun setUpMocks() = mocker.injectMocks(this) // <2>
@Mock lateinit var view: View
@Fake lateinit var model: Model
Expand All @@ -38,6 +45,8 @@ class MyTest : TestsWithMocks() {
}
}
----
<1> This is mandatory and cannot be generated. You need to run the KSP generation at least once for your IDE to see the `injectMocks` generated function.
<1> The `TestsWithMocks` super class eases the use of MocKMP in your tests, but is not mandatory.
<2> This is mandatory and cannot be generated. You need to build your project at least once for your IDE to see the `injectMocks` function.
--
+
NOTE: Every property annotated by `@Mock`, annotated by `@Fake` or delegated to `withMocks` will be reset fresh between each test.
12 changes: 2 additions & 10 deletions doc/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,7 @@ A Kotlin/Multiplatform Kotlin Symbol Processor that generates Mocks & Fakes at c
====

A Mock is an object implementing an interface whose behaviour is configurable at run-time, usually specifically for unit-tests. +
A mocks can be used to validate that a method or property was (or wasn't) accessed a certain number of times.
Mocks can be used to validate that a method or property was (or wasn't) accessed in specific conditions.

A fake is a concrete class that contains bogus data: all nullables are null, all strings are empty, all numbers are 0. +
A fake can be used when you need to instanciate a class to test an API but do not care about the data it contains.

[NOTE]
====
MocKMP uses https://github.com/google/ksp[KSP], which has limited support for Kotlin/Multiplatform.
In particular, https://github.com/google/ksp/issues/567[KSP does not support generating code for commonTest].
In order to work on Kotlin/Multiplatform projects, the MocKMP plugin uses a trick that consist of only generating code for the JVM, and then using the generated code for all targets.
====
A fake can be used when you need to instantiate a class to test an API but do not care about the data it contains.
52 changes: 48 additions & 4 deletions doc/modules/core/pages/facking.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,31 @@ CAUTION: Only *concrete trees* (concrete classes containing concrete classes) ca

== Requesting generation

You can declare that a class needs a specific faked data by using the `@UsesFakes` annotation.
You can declare that a class or function needs a specific faked data by using the `@UsesFakes` annotation.

[source,kotlin]
----
@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 calling its `fake*` corresponding function:
Once a class has been faked, you can get a new instance by the `fake` function:

[source,kotlin]
----
@UsesFakes(User::class)
class MyTests {
val user = fakeUser()
val user = fake<User>()
}
----

Expand All @@ -44,7 +49,7 @@ By using a `data class`, you can easily tweak your fakes according to your needs
[source,kotlin]
----
val user = fakeUser().copy(id = 42)
val user = fake<User>().copy(id = 42)
----
====

Expand All @@ -61,3 +66,42 @@ fun provideFakeInstant() = Instant.fromEpochSeconds(0)
----

CAUTION: 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`:

[source,kotlin]
----
data class NullGenData<T>(val content: T)
data class NonNullGenData<T : Any>(val content: T)
@Test
@UsesFakes(GenData::class)
fun testGenericFake() {
val nullData = fake<NullGenData<*>>()
assertNull(nullData.content)
val nonNullData = fake<NonNullGenData<*>>()
assertNonNull(nonNullData.content) // is Any
}
----

However, if you need a specific generic type to fake, you need to declare it in an xref:injection.adoc[injected class], even if you are never going to use that class.

[source,kotlin]
----
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)
}
----
17 changes: 9 additions & 8 deletions doc/modules/core/pages/helper.adoc
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
= The test class helper
= The test helper

== The `TestsWithMocks` abstract class

MocKMP provides the `TestsWithMocks` helper class that your test classes can inherit from.
It provides the following benefits:

- Provides a `Mocker`.
- Resets the `Mocker` before each tests.
- Resets the `Mocker` before each test.
- Provides `withMocks` property delegates to initialize objects with mocks.
- Allows to call `every`, `everySuspending`, `verify`, and `verifyWithSuspend` without `mocker.`.

It does not come with the standard runtime (as it forces the dependency to JUnit on the JVM), so to use it you need to either:

* define `usesHelper = true` in the MocKMP Gradle plulgin configuration block,
* or add the `mockmp-test-helper` implementation dependency.
It does not come with the standard runtime (as it forces the dependency to JUnit on the JVM), so to use it you need to call `withHelper()` in the xref:setup.adoc#mockmp-gradle-config[MocKMP Gradle plugin configuration block].

Here's a test class example with `TestsWithMocks`:

[source,kotlin]
----
@UsesFakes(User::class)
class MyTests : TestsWithMocks() { //<1>
override fun setUpMocks() = injectMocks(mocker) //<2>
override fun setUpMocks() = mocker.injectMocks(this) //<2>
@Mock lateinit var db: Database
@Mock lateinit var api: API
Expand All @@ -44,7 +43,7 @@ NOTE: Properties delegated to `withMocks` will be (re)initialized *before each t

[CAUTION]
====
Because of https://youtrack.jetbrains.com/issue/KT-54932[this issue], you cannot consider that the mocks have been initialized in yout `@BeforeTest` methods.
Because of https://youtrack.jetbrains.com/issue/KT-54932[this issue], you cannot consider that the mocks have been initialized in your other `@BeforeTest` methods.
You can override `initMocksBeforeTest` if you need to initialize your mocks before each test:
[source,kotlin]
Expand All @@ -58,6 +57,8 @@ class MyTests : TestsWithMocks() {
----
====

== The `ITestsWithMocks` interface

In case your test class already extends another class, you can use the `ITestsWithMocks` interface instead:

[source,kotlin]
Expand Down
22 changes: 18 additions & 4 deletions doc/modules/core/pages/injection.adoc
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
= Injecting your test classes

Instead of manually creating your own mocks & fakes, it can be useful to inject them in your test class, especially if you have multiple tests using them.
Instead of manually creating your own mocks & fakes, it can be useful to inject them in your test classes, especially if you have multiple tests using them.

[source,kotlin]
----
@UsesFakes(User::class)
class MyTests {
@Mock lateinit var db: Database
@Mock lateinit var api: API
Expand All @@ -17,7 +16,7 @@ class MyTests {
@BeforeTest fun setUp() {
mocker.reset() //<1>
this.injectMocks(mocker) //<2>
mocker.injectMocks(this) //<2>
controller = ControllerImpl(db, api) //<3>
}
Expand All @@ -32,6 +31,21 @@ class MyTests {
<2> Injects mocks and fakes.
<3> Create classes to be tested with injected mocks & fakes.

As soon as a class `T` contains a `@Mock` or `@Fake` annotated property, a `T.injectMocks(Mocker)` function will be created by the processor.
As soon as a class `T` contains a `@Mock` or `@Fake` annotated property, a `Mocker.injectMocks(receiver: T)` function will be created by the processor.

IMPORTANT: Don't forget to `reset` the `Mocker` in a `@BeforeTest` method!

[TIP]
====
You can inject other classes than test classes:
[source,kotlin]
----
class MyMocks {
@Mock lateinit var db: Database
@Mock lateinit var api: API
}
----
...will generate the `Mocker.injectMocks(receiver: MyMocks)` function.
====
Loading

0 comments on commit 959285c

Please sign in to comment.