Skip to content

Commit

Permalink
Merge pull request #34 from FredDeschenes/mockk-junit5
Browse files Browse the repository at this point in the history
Mockk junit5
  • Loading branch information
oleksiyp committed Feb 25, 2018
2 parents 37cc5f6 + 1b4ef8c commit ae10396
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 3 deletions.
34 changes: 33 additions & 1 deletion README.md
Expand Up @@ -79,7 +79,7 @@ verify { car.drive(Direction.NORTH) }

Use can use annotations to simplify creation of mock objects:

```
```kotlin
class Test {
@MockK
lateinit var car1: Car
Expand All @@ -100,6 +100,38 @@ class Test {
}
```

#### JUnit5

By adding the `mockk-junit5` module to your project, you can rewrite the previous example as:

```kotlin
@ExtendWith(MockKJUnit5Extension::class)
class Test {
@MockK
lateinit var car1: Car

@RelaxedMockK
lateinit var car2: Car

@SpyK
val car3 = Car()

@Test
fun calculateAddsValues1() {
// ... use car1, car2 and car3
}
}
```

You can also use `@MockK` and `@RelaxedMockK` on test function parameters:

```kotlin
@Test
fun calculateAddsValues1(@MockK car1: Car, @RelaxedMockK car2: Car) {
// ... use car1 and car2
}
```

### Spy

Spies allow to mix mocks and real objects.
Expand Down
2 changes: 0 additions & 2 deletions agent/build.gradle
Expand Up @@ -5,8 +5,6 @@ plugins {
dependencies {
compileOnly "junit:junit:4.12"
compileOnly 'org.testng:testng:6.8'
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.0.1'
compileOnly 'org.junit.platform:junit-platform-launcher:1.0.1'
compile 'net.bytebuddy:byte-buddy:1.7.9'
compileOnly 'net.bytebuddy:byte-buddy-agent:1.7.9'
runtime 'net.bytebuddy:byte-buddy-agent:1.7.9'
Expand Down
95 changes: 95 additions & 0 deletions mockk-junit5/build.gradle
@@ -0,0 +1,95 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.junit.platform:junit-platform-gradle-plugin:1.0.3"
}
}
plugins {
id "jacoco"
}
apply plugin: "java"
apply plugin: "kotlin-platform-jvm"
apply plugin: "org.junit.platform.gradle.plugin"

ext.junitJupiterVersion = '5.0.3'

dependencies {
compileOnly project(":mockk")

compileOnly "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"

compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compileOnly "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

testCompile project(":mockk")

testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

testCompile "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"
testCompile "org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion"
}

task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allSource
}

artifacts {
archives sourcesJar
}

jacocoTestReport {
reports {
xml.enabled true
html.enabled true
}
}
check.dependsOn jacocoTestReport

uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: rootProject.properties['ossrhUsername'] ?: '', password: rootProject.properties['ossrhPassword'] ?: '')
}

snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: rootProject.properties['ossrhUsername'] ?: '', password: rootProject.properties['ossrhPassword'] ?: '')
}

pom.project {
name 'MockK'
packaging 'jar'
description 'mocking library for Kotlin'
url 'http://mockk.io'

scm {
connection 'scm:git:git@github.com:oleksiyp/mockk.git'
developerConnection 'scm:git:git@github.com:oleksiyp/mockk.git'
url 'http://www.github.com/oleksiyp/mockk/'
}

licenses {
license {
name 'Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0'
}
}

developers {
developer {
id 'oleksiyp'
name 'Oleksii Pylypenko'
email 'oleksiy.pylypenko@gmail.com'
}
}
}
}
}
}

@@ -0,0 +1,62 @@
package io.mockk.junit5

import io.mockk.MockKAnnotations
import io.mockk.MockKGateway
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.impl.annotations.SpyK
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import org.junit.jupiter.api.extension.TestInstancePostProcessor
import java.lang.reflect.Parameter

/**
* JUnit5 extension based on the Mockito extension found in JUnit5's samples.
* Allows using the [MockK] and [RelaxedMockK] on class properties and test function parameters,
* as well as [SpyK] on class properties.
*/
class MockKJUnit5Extension : TestInstancePostProcessor, ParameterResolver {
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
val parameter = parameterContext.parameter
return parameter.isAnnotationPresent(MockK::class.java)
|| parameter.isAnnotationPresent(RelaxedMockK::class.java)
}

override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any? {
return getMock(parameterContext.parameter, extensionContext)
}

private fun getMock(parameter: Parameter, extensionContext: ExtensionContext): Any? {
val mockType = parameter.type

return getMockKAnnotation(parameter)?.let { annotation ->
val mockName = getMockName(parameter, mockType, annotation)
io.mockk.MockK.useImpl {
MockKGateway.implementation().mockFactory.mockk(mockType.kotlin, mockName, annotation is RelaxedMockK, arrayOf())
}
}
}

private fun getMockKAnnotation(parameter: Parameter): Any? {
return parameter.getAnnotation(MockK::class.java) ?: parameter.getAnnotation(RelaxedMockK::class.java)
}

private fun getMockName(parameter: Parameter, mockType: Class<*>, annotation: Any): String {
val fromAnnotation = when (annotation) {
is MockK -> annotation.name.trim()
is RelaxedMockK -> annotation.name.trim()
else -> ""
}

return when {
fromAnnotation.isNotEmpty() -> fromAnnotation
parameter.isNamePresent -> parameter.name
else -> mockType.canonicalName
}
}

override fun postProcessTestInstance(testInstance: Any, context: ExtensionContext) {
MockKAnnotations.init(testInstance)
}
}
@@ -0,0 +1,103 @@
package io.mockk.junit5

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.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

enum class Direction {
NORTH,
SOUTH,
EAST,
WEST
}

enum class Outcome {
FAILURE,
RECORDED
}

class RelaxedOutcome

class Car {
@Suppress("UNUSED_PARAMETER")
fun recordTelemetry(speed: Int, direction: Direction, lat: Double, long: Double): Outcome {
println("REAL METHOD CALLED")
return Outcome.FAILURE
}

fun relaxedTest(): RelaxedOutcome? {
return null
}
}

@ExtendWith(MockKJUnit5Extension::class)
class MockKJUnit5ExtensionTest {
@MockK
private lateinit var car2: Car

@RelaxedMockK
private lateinit var relaxedCar: Car

@SpyK
private var carSpy = Car()

@Test
fun injectsValidMockInMethods(@MockK car: Car) {
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)

Assertions.assertEquals(Outcome.RECORDED, result)
}

@Test
fun injectsValidMockInClass() {
every {
car2.recordTelemetry(
speed = more(50),
direction = Direction.NORTH,
lat = any(),
long = any()
)
} returns Outcome.RECORDED

val result = car2.recordTelemetry(51, Direction.NORTH, 1.0, 2.0)

Assertions.assertEquals(Outcome.RECORDED, result)
}

@Test
fun injectsValidRelaxedMockInMethods(@RelaxedMockK car: Car) {
val result = car.relaxedTest()

Assertions.assertTrue(result is RelaxedOutcome)
}

@Test
fun injectsValidRelaxedMockInClass() {
val result = relaxedCar.relaxedTest()

Assertions.assertTrue(result is RelaxedOutcome)
}

@Test
fun testInjectsValidSpyInClass() {
val result = carSpy.relaxedTest()

Assertions.assertNull(result)

verify { carSpy.relaxedTest() }
}
}
1 change: 1 addition & 0 deletions settings.gradle
Expand Up @@ -7,6 +7,7 @@ include 'mockk-agent'
include 'mockk-dsl'
include 'mockk-dsl-jvm'
include 'mockk-dsl-js'
include 'mockk-junit5'

project(":mockk-agent").projectDir = file("agent")
project(":mockk-common").projectDir = file("common")
Expand Down

0 comments on commit ae10396

Please sign in to comment.