Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a JUnit 4 rule #816

Merged
merged 6 commits into from May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions mockk/jvm/build.gradle.kts
Expand Up @@ -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())
}
Expand Down
44 changes: 44 additions & 0 deletions 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()
}
}
87 changes: 87 additions & 0 deletions 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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need RunWith(JUnit4::class) here for the tests to pass

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

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