diff --git a/README.md b/README.md index 77bc28cd1..09f80e3af 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,30 @@ To change this, use `overrideValues = true`. This would assign the value even if To inject `val`s, use `injectImmutable = true`. For a shorter notation use `@OverrideMockKs` which does the same as `@InjectMockKs` by default, but turns these two flags on. +### JUnit4 + +JUnit 4 exposes a rule-based API to allow for some automation following the test lifecycle. MockK includes a rule which uses this to set up and tear down your mocks without needing to manually call `MockKAnnotations.init(this)`. Example: + +```kotlin +class CarTest { + @get:Rule + val mockkRule = MockKRule(this) + + @MockK + lateinit var car1: Car + + @RelaxedMockK + lateinit var car2: Car + + @Test + fun something() { + every { car1.drive() } just runs + every { car2.changeGear(any()) } returns true + // etc + } +} +``` + #### JUnit5 In JUnit5 you can use `MockKExtension` to initialize your mocks. diff --git a/mockk/jvm/build.gradle.kts b/mockk/jvm/build.gradle.kts index 15eb45b05..7110a98c7 100644 --- a/mockk/jvm/build.gradle.kts +++ b/mockk/jvm/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(Deps.Libs.kotlinReflect(kotlinVersion())) compileOnly(Deps.Libs.kotlinCoroutinesCore()) compileOnly("org.slf4j:slf4j-api:1.7.26") + compileOnly("junit:junit:4.13.1") testImplementation(Deps.Libs.kotlinCoroutinesCore()) } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/junit4/MockKRule.kt b/mockk/jvm/src/main/kotlin/io/mockk/junit4/MockKRule.kt new file mode 100644 index 000000000..c3e05ee40 --- /dev/null +++ b/mockk/jvm/src/main/kotlin/io/mockk/junit4/MockKRule.kt @@ -0,0 +1,44 @@ +package io.mockk.junit4 + +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.unmockkAll +import org.junit.rules.TestRule +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +/** + * A simple JUnit 4 rule which handles the setting up and tearing down of mock objects using the + * [MockK] or [RelaxedMockK] annotations at the beginning and end of each test respectively - + * without needing to manually call [unmockkAll] or [MockKAnnotations.init]. + * + * Example: + * + * ``` + * class ExampleTest { + * @get:Rule + * val mockkRule = MockKRule(this) + * + * @MockK + * private lateinit var car: Car + * + * @Test + * fun something() { + * every { car.drive() } just runs + * ... + * } + * } + * ``` + */ +class MockKRule(private val testSubject: Any) : TestWatcher(), TestRule { + override fun starting(description: Description?) { + super.starting(description) + MockKAnnotations.init(testSubject) + } + + override fun finished(description: Description?) { + super.finished(description) + unmockkAll() + } +} diff --git a/mockk/jvm/src/test/kotlin/io/mockk/junit4/MockKRuleTest.kt b/mockk/jvm/src/test/kotlin/io/mockk/junit4/MockKRuleTest.kt new file mode 100644 index 000000000..96b904da7 --- /dev/null +++ b/mockk/jvm/src/test/kotlin/io/mockk/junit4/MockKRuleTest.kt @@ -0,0 +1,87 @@ +@file:Suppress("UNUSED_PARAMETER") + +package io.mockk.junit4 + +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.impl.annotations.SpyK +import io.mockk.verify +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class MockKRuleTest { + + @get:Rule + val mockkRule = MockKRule(this) + + enum class Direction { + NORTH, + SOUTH, + EAST, + WEST + } + + enum class Outcome { + FAILURE, + RECORDED + } + + class RelaxedOutcome + + class Car { + fun recordTelemetry(speed: Int, direction: Direction, lat: Double, long: Double): Outcome { + return Outcome.FAILURE + } + + fun relaxedTest(): RelaxedOutcome? { + return null + } + } + + @MockK + private lateinit var car: Car + + @RelaxedMockK + private lateinit var relaxedCar: Car + + @SpyK + private var carSpy = Car() + + @Test + fun injectsValidMockInClass() { + every { + car.recordTelemetry( + speed = more(50), + direction = Direction.NORTH, + lat = any(), + long = any() + ) + } returns Outcome.RECORDED + + val result = car.recordTelemetry(51, Direction.NORTH, 1.0, 2.0) + + assertEquals(Outcome.RECORDED, result) + } + + @Test + fun injectsValidRelaxedMockInClass() { + val result = relaxedCar.relaxedTest() + + assertTrue(result is RelaxedOutcome) + } + + @Test + fun testInjectsValidSpyInClass() { + val result = carSpy.relaxedTest() + + assertNull(result) + + verify { carSpy.relaxedTest() } + } +}