Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
with
457 additions
and 22 deletions.
- +1 −1 .travis.yml
- +3 −3 build.gradle
- +2 −2 doc/release-notes/official.md
- +2 −0 gradle/dependencies.gradle
- +12 −0 gradle/root/java-compatibility-check.gradle
- +0 −13 gradle/root/java6-compatibility.gradle
- BIN gradle/wrapper/gradle-wrapper.jar
- +2 −2 gradle/wrapper/gradle-wrapper.properties
- +1 −0 settings.gradle
- +6 −0 src/main/java/org/mockito/Mockito.java
- +1 −1 src/main/java/org/mockito/session/MockitoSessionBuilder.java
- +17 −0 subprojects/junit-jupiter/junit-jupiter.gradle
- +165 −0 subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java
- +26 −0 subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoSettings.java
- +73 −0 subprojects/junit-jupiter/src/test/java/org/mockitousage/JunitJupiterTest.java
- +146 −0 subprojects/junit-jupiter/src/test/java/org/mockitousage/StrictnessTest.java
@@ -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" | ||
} | ||
} | ||
} | ||
} |
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 |
@@ -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>@ExtendWith(MockitoExtension.class)</b> | ||
* public class ExampleTest { | ||
* | ||
* @Mock | ||
* private List list; | ||
* | ||
* @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>@MockitoSettings(strictness = Strictness.STRICT_STUBS)</b> | ||
* public class ExampleTest { | ||
* | ||
* @Mock | ||
* private List list; | ||
* | ||
* @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.