Permalink
Browse files

Create a MockitoExtension for JUnit Jupiter (JUnit 5+) (#1221)

* created junit5 subproject

* fixed dependencies

* Introduced MockitoSession-API

* Removed @nonnull due to compile errors

* Added more JUnit5 specific tests

* Added stricness option per test class or method

* fixed tests cases

* fixed tests cases and updated dependencies

* using the extension context now to store the current mockito session

* Update junit5.gradle

* going back to jupiter-api

* Update dependencies.gradle

* Update junit5.gradle

* incorporated review issues

* removed session from store after each test

* Ensure build passes with removing Java 6 compatibility for junit5

* added work around to initialize mocks of nested-test parent classes

* added more test cases

* collecting parent test instances via TestInstancePostProcessor, removed reflection workaround

* fixed checkstyle error unused import

* fixed tests cases

* fixed javadoc compile errors

* Refactor testMethodName for full list of instances

* Refactor handling of strictness value in mockito extension

* Remove WithMockito annotation

* Fix typo in test name

* Fix compilation error

* Use optional.map instead of if's

* rebased on release/2.x

* Polish up implementation with ConfiguredWithMockito

* Fix gradle test execution

* Use Junit Jupiter instead of JUnit5 and update Gradle wrapper version

* Update Animal Sniffer to also sniff for Java 8 projects

* Process review feedback

* Update default value for ConfigureWithMockito

* Process @mockitoguy feedback
  • Loading branch information...
ChristianSchwarz authored and TimvdLippe committed Mar 24, 2018
1 parent 2b1a6e0 commit b851b93f1bb06a2a681cebb8aa797a96255737bd
View
@@ -38,7 +38,7 @@ script:
# We are using && below on purpose
# ciPerformRelease must run only when the entire build has completed
# This guarantees that no release steps are executed when the build or tests fail
- ./gradlew build idea -s -PcheckJava6Compatibility && ./gradlew ciPerformRelease
- ./gradlew build idea -s -PcheckJavaCompatibility && ./gradlew ciPerformRelease
after_success:
#Generates coverage report:
View
@@ -7,7 +7,7 @@ buildscript {
dependencies {
classpath 'gradle.plugin.nl.javadude.gradle.plugins:license-gradle-plugin:0.14.0'
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.3.0' //for 'java6-compatibility.gradle'
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.3.0' //for 'java-compatibility-check.gradle'
//Using buildscript.classpath so that we can resolve shipkit from maven local, during local testing
classpath 'org.shipkit:shipkit:0.9.144'
@@ -30,7 +30,7 @@ allprojects {
apply from: 'gradle/root/ide.gradle'
apply from: 'gradle/root/gradle-fix.gradle'
apply from: 'gradle/root/java6-compatibility.gradle'
apply from: 'gradle/root/java-compatibility-check.gradle'
apply from: 'gradle/java-library.gradle'
apply from: 'gradle/license.gradle'
apply from: 'gradle/root/coverage.gradle'
@@ -81,7 +81,7 @@ dependencies {
}
task wrapper(type: Wrapper) {
gradleVersion = '3.5'
gradleVersion = '4.6'
}
//Posting Build scans to https://scans.gradle.com
@@ -37,13 +37,13 @@
# 2.15.0 (2018-02-10) published to [JCenter](https://bintray.com/mockito/maven/mockito/2.15.0)/[Maven Central](http://search.maven.org/#artifactdetails%7Corg.mockito%7Cmockito-core%7C2.15.0%7Cjar)
Notable changes:
- new APIs to ```MockitoSession``` feature that can be used by framework integrations such as JUnit5 Jupiter.
- new APIs to ```MockitoSession``` feature that can be used by framework integrations such as JUnit Jupiter.
- ```InvocationFactory``` API improvements driven by integrations with Android.
- Javadoc updates.
**2.15.0 (2018-02-10)** - [16 commits](https://github.com/mockito/mockito/compare/v2.14.0...v2.15.0) by [Marc Philipp](https://github.com/marcphilipp) (10), [Szczepan Faber](http://github.com/mockitoguy) (6) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.15.0-green.svg)](https://bintray.com/mockito/maven/mockito2.15.0)
- Extend MockitoSession(Builder) API to enable usage by testing frameworks [(#1301)](https://github.com/mockito/mockito/pull/1301)
- [JUnit5] MockitoSession#initMocks should support multiple test instances [(#1232)](https://github.com/mockito/mockito/issues/1232)
- [JUnit Jupiter] MockitoSession#initMocks should support multiple test instances [(#1232)](https://github.com/mockito/mockito/issues/1232)
- Design MockitoSession API improvements for unit test frameworks [(#898)](https://github.com/mockito/mockito/issues/898)
**2.14.0 (2018-02-09)** - [12 commits](https://github.com/mockito/mockito/compare/v2.13.3...v2.14.0) by 4 authors - published to [![Bintray](https://img.shields.io/badge/Bintray-2.14.0-green.svg)](https://bintray.com/mockito/maven/mockito-development2.14.0)
@@ -7,6 +7,8 @@ def versions = [:]
versions.bytebuddy = '1.8.0'
libraries.junit4 = 'junit:junit:4.12'
libraries.junitJupiterLauncher = 'org.junit.platform:junit-platform-launcher:1.1.0'
libraries.junitJupiterEngine = 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
libraries.assertj = 'org.assertj:assertj-core:2.9.0'
libraries.hamcrest = 'org.hamcrest:hamcrest-core:1.3'
libraries.testng = 'org.testng:testng:6.3.1'
@@ -0,0 +1,12 @@
//Use animal sniffer to detect Java incompatibilities, as we build on newer JDKs
//if we're skipping release, let's also skip checking compatibility (faster builds)
if (project.hasProperty('checkJavaCompatibility') && !System.getenv("SKIP_RELEASE")) {
allprojects { p ->
plugins.withId('java') {
p.apply plugin: 'ru.vyarus.animalsniffer'
p.dependencies {
signature "org.codehaus.mojo.signature:java1${p.name != 'junit-jupiter' ? '6' : '8'}:1.0@signature"
}
}
}
}

This file was deleted.

Oops, something went wrong.
View
BIN -379 Bytes (99%) gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
@@ -1,5 +1,5 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
View
@@ -4,6 +4,7 @@ include 'testng'
include 'extTest'
include 'kotlinTest'
include 'android'
include 'junit-jupiter'
rootProject.name = 'mockito'
@@ -99,6 +99,7 @@
* <a href="#42">42. (**new**) New API for integrations: listening on verification start events (Since 2.11.+)</a><br/>
* <a href="#43">43. (**new**) New API for integrations: <code>MockitoSession</code> is usable by testing frameworks (Since 2.15.+)</a><br/>
* <a href="#44">44. Deprecated <code>org.mockito.plugins.InstantiatorProvider</code> as it was leaking internal API. it was replaced by <code>org.mockito.plugins.InstantiatorProvider2 (Since 2.15.4)</code></a><br/>
* <a href="#45">45. (**new**) New JUnit Jupiter (JUnit5+) extension</a><br/>
* </b>
*
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
@@ -1506,6 +1507,11 @@
* <p>{@link org.mockito.plugins.InstantiatorProvider} returned an internal API. Hence it was deprecated and replaced
* by {@link org.mockito.plugins.InstantiatorProvider2}. Old {@link org.mockito.plugins.InstantiatorProvider
* instantiator providers} will continue to work, but it is recommended to switch to the new API.</p>
*
* <h3 id="45">45. (**new**) <a class="meaningful_link" href="#junit5_mockito" name="junit5_mockito">New JUnit Jupiter (JUnit5+) extension</a></h3>
*
* For integration with JUnit Jupiter (JUnit5+), use the `org.mockito.junit-jupiter` artifact.
* For more information about the usage of the integration, see the JavaDoc of <code>MockitoExtension</code>.
*/
@SuppressWarnings("unchecked")
public class Mockito extends ArgumentMatchers {
@@ -47,7 +47,7 @@
* like {@link org.mockito.Mock}.
* <p>
* In most scenarios, you only need to init mocks on a single test class instance.
* This method is useful for advanced framework integrations (like JUnit5), when a test uses multiple, e.g. nested, test class instances.
* This method is useful for advanced framework integrations (like JUnit Jupiter), when a test uses multiple, e.g. nested, test class instances.
* <p>
* This method calls {@link #initMocks(Object)} for each passed test class instance.
*
@@ -0,0 +1,17 @@
description = "Mockito JUnit 5 support"
apply from: "$rootDir/gradle/java-library.gradle"
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile project.rootProject
testCompile libraries.assertj
testCompile libraries.junitJupiterLauncher
implementation libraries.junitJupiterEngine
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2018 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.junit.jupiter;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.quality.Strictness;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
/**
* Extension that initializes mocks and handles strict stubbings. This extension is the JUnit Jupiter equivalent
* of our JUnit4 {@link MockitoJUnitRunner}.
*
* Example usage:
*
* <pre class="code"><code class="java">
* <b>&#064;ExtendWith(MockitoExtension.class)</b>
* public class ExampleTest {
*
* &#064;Mock
* private List list;
*
* &#064;Test
* public void shouldDoSomething() {
* list.add(100);
* }
* }
* </code></pre>
*
* If you would like to configure the used strictness for the test class, use {@link MockitoSettings}.
*
* <pre class="code"><code class="java">
* <b>&#064;MockitoSettings(strictness = Strictness.STRICT_STUBS)</b>
* public class ExampleTest {
*
* &#064;Mock
* private List list;
*
* &#064;Test
* public void shouldDoSomething() {
* list.add(100);
* }
* }
* </code></pre>
*/
public class MockitoExtension implements TestInstancePostProcessor,BeforeEachCallback, AfterEachCallback {
private final static Namespace MOCKITO = create("org.mockito");
private final static String SESSION = "session";
private final static String TEST_INSTANCE = "testInstance";
private final Strictness strictness;
// This constructor is invoked by JUnit Jupiter via reflection
@SuppressWarnings("unused")
private MockitoExtension() {
this(Strictness.STRICT_STUBS);
}
private MockitoExtension(Strictness strictness) {
this.strictness = strictness;
}
/**
* Callback for post-processing the supplied test instance.
* <p>
* <p><strong>Note</strong>: the {@code ExtensionContext} supplied to a
* {@code TestInstancePostProcessor} will always return an empty
* {@link Optional} value from {@link ExtensionContext#getTestInstance()
* getTestInstance()}. A {@code TestInstancePostProcessor} should therefore
* only attempt to process the supplied {@code testInstance}.
*
* @param testInstance the instance to post-process; never {@code null}
* @param context the current extension context; never {@code null}
*/
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context){
context.getStore(MOCKITO).put(TEST_INSTANCE, testInstance);
}
/**
* Callback that is invoked <em>before</em> each test is invoked.
*
* @param context the current extension context; never {@code null}
*/
@Override
public void beforeEach(final ExtensionContext context) {
List<Object> testInstances = new LinkedList<>();
testInstances.add(context.getRequiredTestInstance());
this.collectParentTestInstances(context, testInstances);
Strictness actualStrictness = this.retrieveAnnotationFromTestClasses(context)
.map(MockitoSettings::strictness)
.orElse(strictness);
MockitoSession session = Mockito.mockitoSession()
.initMocks(testInstances.toArray())
.strictness(actualStrictness)
.startMocking();
context.getStore(MOCKITO).put(SESSION, session);
}
private Optional<MockitoSettings> retrieveAnnotationFromTestClasses(final ExtensionContext context) {
ExtensionContext currentContext = context;
Optional<MockitoSettings> annotation;
do {
annotation = findAnnotation(currentContext.getElement(), MockitoSettings.class);
if (!currentContext.getParent().isPresent()) {
break;
}
currentContext = currentContext.getParent().get();
} while (!annotation.isPresent() && currentContext != context.getRoot());
return annotation;
}
private void collectParentTestInstances(ExtensionContext context, List<Object> testInstances) {
Optional<ExtensionContext> parent = context.getParent();
while (parent.isPresent() && parent.get() != context.getRoot()) {
ExtensionContext parentContext = parent.get();
Object testInstance = parentContext.getStore(MOCKITO).remove(TEST_INSTANCE);
if (testInstance != null) {
testInstances.add(testInstance);
}
parent = parentContext.getParent();
}
}
/**
* Callback that is invoked <em>after</em> each test has been invoked.
*
* @param context the current extension context; never {@code null}
*/
@Override
public void afterEach(ExtensionContext context) {
context.getStore(MOCKITO).remove(SESSION, MockitoSession.class)
.finishMocking();
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.junit.jupiter;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.quality.Strictness;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation that can configure Mockito as invoked by the {@link MockitoExtension}.
*/
@ExtendWith(MockitoExtension.class)
@Retention(RUNTIME)
public @interface MockitoSettings {
/**
* Configure the strictness used in this test.
* @return The strictness to configure, by default {@link Strictness#STRICT_STUBS}
*/
Strictness strictness() default Strictness.STRICT_STUBS;
}
Oops, something went wrong.

0 comments on commit b851b93

Please sign in to comment.