Skip to content

Commit

Permalink
Fixed \#1967: Correctly handle mocks with limited life-cycle in liste…
Browse files Browse the repository at this point in the history
…ners.
  • Loading branch information
raphw committed Jul 16, 2020
1 parent 05b39bf commit 9123d1e
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 43 deletions.
3 changes: 2 additions & 1 deletion settings.gradle.kts
Expand Up @@ -10,6 +10,7 @@ include("deprecatedPluginsTest",
"android",
"junit-jupiter",
"junitJupiterExtensionTest",
"junitJupiterInlineMockMakerExtensionTest",
"module-test",
"memory-test",
"errorprone",
Expand All @@ -18,7 +19,7 @@ include("deprecatedPluginsTest",

rootProject.name = "mockito"

val koltinBuildScriptProject = hashSetOf("junitJupiterExtensionTest")
val koltinBuildScriptProject = hashSetOf("junitJupiterExtensionTest", "junitJupiterInlineMockMakerExtensionTest")

fun buildFileExtensionFor(projectName: String) =
if (projectName in koltinBuildScriptProject) ".gradle.kts" else ".gradle"
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mockito/MockedStatic.java
Expand Up @@ -63,6 +63,16 @@ default void verify(Verification verification) {
*/
void verifyNoInteractions();

/**
* Checks if this mock is closed.
*
* @return {@code true} if this mock is closed.
*/
boolean isClosed();

/**
* Releases this static mock and throws a {@link org.mockito.exceptions.base.MockitoException} if closed already.
*/
@Override
void close();

Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/mockito/Mockito.java
Expand Up @@ -2058,6 +2058,12 @@ public static <T> T spy(Class<T> classToSpy) {
* The returned object's {@link MockedStatic#close()} method must be called upon completing the
* test or the mock will remain active on the current thread.
* <p>
* <b>Note</b>: We recommend against mocking static methods of classes in the standard library or
* classes used by custom class loaders used to executed the block with the mocked class. A mock
* maker might forbid mocking static methods of know classes that are known to cause problems.
* Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not
* explicitly forbidden.
* <p>
* See examples in javadoc for {@link Mockito} class
*
* @param classToMock class or interface of which static mocks should be mocked.
Expand All @@ -2074,6 +2080,12 @@ public static <T> MockedStatic<T> mockStatic(Class<T> classToMock) {
* The returned object's {@link MockedStatic#close()} method must be called upon completing the
* test or the mock will remain active on the current thread.
* <p>
* <b>Note</b>: We recommend against mocking static methods of classes in the standard library or
* classes used by custom class loaders used to executed the block with the mocked class. A mock
* maker might forbid mocking static methods of know classes that are known to cause problems.
* Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not
* explicitly forbidden.
* <p>
* See examples in javadoc for {@link Mockito} class
*
* @param classToMock class or interface of which static mocks should be mocked.
Expand All @@ -2091,6 +2103,12 @@ public static <T> MockedStatic<T> mockStatic(Class<T> classToMock, Answer defaul
* The returned object's {@link MockedStatic#close()} method must be called upon completing the
* test or the mock will remain active on the current thread.
* <p>
* <b>Note</b>: We recommend against mocking static methods of classes in the standard library or
* classes used by custom class loaders used to executed the block with the mocked class. A mock
* maker might forbid mocking static methods of know classes that are known to cause problems.
* Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not
* explicitly forbidden.
* <p>
* See examples in javadoc for {@link Mockito} class
*
* @param classToMock class or interface of which static mocks should be mocked.
Expand All @@ -2108,6 +2126,12 @@ public static <T> MockedStatic<T> mockStatic(Class<T> classToMock, String name)
* The returned object's {@link MockedStatic#close()} method must be called upon completing the
* test or the mock will remain active on the current thread.
* <p>
* <b>Note</b>: We recommend against mocking static methods of classes in the standard library or
* classes used by custom class loaders used to executed the block with the mocked class. A mock
* maker might forbid mocking static methods of know classes that are known to cause problems.
* Also, if a static method is a JVM-intrinsic, it cannot typically be mocked even if not
* explicitly forbidden.
* <p>
* See examples in javadoc for {@link Mockito} class
*
* @param classToMock class or interface of which static mocks should be mocked.
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mockito/internal/MockedStaticImpl.java
Expand Up @@ -157,6 +157,11 @@ public void verifyNoInteractions() {
noInteractions().verify(data);
}

@Override
public boolean isClosed() {
return closed;
}

@Override
public void close() {
assertNotClosed();
Expand All @@ -181,4 +186,9 @@ private void assertNotClosed() {
"is already resolved and cannot longer be used"));
}
}

@Override
public String toString() {
return "static mock for " + control.getType().getTypeName();
}
}
Expand Up @@ -58,21 +58,25 @@ public static Object processAnnotationForMock(
}
}

private static Class<?> inferStaticMock(Type type, String name) {
static Class<?> inferStaticMock(Type type, String name) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return (Class<?>) parameterizedType.getRawType();
} else {
throw new MockitoException(
join(
"Mockito cannot infer a static mock from a raw type for " + name,
"",
"Instead of @Mock MockedStatic you need to specify a parameterized type",
"For example, if you would like to mock static methods of Sample.class, specify",
"",
"@Mock MockedStatic<Sample>",
"",
"as the type parameter"));
Type[] arguments = parameterizedType.getActualTypeArguments();
if (arguments.length == 1) {
if (arguments[0] instanceof Class<?>) {
return (Class<?>) arguments[0];
}
}
}
throw new MockitoException(
join(
"Mockito cannot infer a static mock from a raw type for " + name,
"",
"Instead of @Mock MockedStatic you need to specify a parameterized type",
"For example, if you would like to mock static methods of Sample.class, specify",
"",
"@Mock MockedStatic<Sample>",
"",
"as the type parameter. If the type is parameterized, it should be specified as raw type."));
}
}
Expand Up @@ -382,6 +382,13 @@ public <T> StaticMockControl<T> createStaticMock(
throw new MockitoException(
"It is not possible to mock static methods of ConcurrentHashMap "
+ "to avoid infinitive loops within Mockito's implementation of static mock handling");
} else if (type == Thread.class
|| type == System.class
|| ClassLoader.class.isAssignableFrom(type)) {
throw new MockitoException(
"It is not possible to mock static methods of "
+ type.getTypeName()
+ "to avoid interfering with the class loading mechanism");
}

bytecodeGenerator.mockClassStatic(type);
Expand Down
Expand Up @@ -43,7 +43,11 @@ public DefaultMockitoSession(
closeables.add(MockitoAnnotations.openMocks(testClassInstance));
}
} catch (RuntimeException e) {
release();
try {
release();
} catch (Throwable t) {
e.addSuppressed(t);
}

// clean up in case 'openMocks' fails
listener.setListenerDirty();
Expand All @@ -58,38 +62,38 @@ public void setStrictness(Strictness strictness) {

@Override
public void finishMocking() {
release();

finishMocking(null);
}

@Override
public void finishMocking(final Throwable failure) {
release();

// Cleaning up the state, we no longer need the listener hooked up
// The listener implements MockCreationListener and at this point
// we no longer need to listen on mock creation events. We are wrapping up the session
Mockito.framework().removeListener(listener);

// Emit test finished event so that validation such as strict stubbing can take place
listener.testFinished(
new TestFinishedEvent() {
@Override
public Throwable getFailure() {
return failure;
}

@Override
public String getTestName() {
return name;
}
});

// Validate only when there is no test failure to avoid reporting multiple problems
if (failure == null) {
// Finally, validate user's misuse of Mockito framework.
Mockito.validateMockitoUsage();
try {
// Cleaning up the state, we no longer need the listener hooked up
// The listener implements MockCreationListener and at this point
// we no longer need to listen on mock creation events. We are wrapping up the session
Mockito.framework().removeListener(listener);

// Emit test finished event so that validation such as strict stubbing can take place
listener.testFinished(
new TestFinishedEvent() {
@Override
public Throwable getFailure() {
return failure;
}

@Override
public String getTestName() {
return name;
}
});

// Validate only when there is no test failure to avoid reporting multiple problems
if (failure == null) {
// Finally, validate user's misuse of Mockito framework.
Mockito.validateMockitoUsage();
}
} finally {
release();
}
}

Expand All @@ -98,7 +102,7 @@ private void release() {
try {
closeable.close();
} catch (Exception e) {
throw new MockitoException("Failed to release mocks", e);
throw new MockitoException("Failed to release " + closeable, e);
}
}
}
Expand Down
Expand Up @@ -42,6 +42,12 @@ public static List<Invocation> find(Iterable<?> mocks) {
public static Set<Stubbing> findStubbings(Iterable<?> mocks) {
Set<Stubbing> stubbings = new TreeSet<Stubbing>(new StubbingComparator());
for (Object mock : mocks) {
// TODO due to the limited scope of static mocks they cannot be processed
// it would rather be required to trigger this stubbing control upon releasing
// the static mock.
if (mock instanceof Class<?>) {
continue;
}
Collection<? extends Stubbing> fromSingleMock =
new DefaultMockingDetails(mock).getStubbings();
stubbings.addAll(fromSingleMock);
Expand Down
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.configuration;

import java.util.List;

import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.exceptions.base.MockitoException;

import static org.assertj.core.api.Assertions.*;

public class MockAnnotationProcessorTest {

@SuppressWarnings("unused")
private MockedStatic<Void> nonGeneric;

@SuppressWarnings("unused")
private MockedStatic<List<?>> generic;

@SuppressWarnings({"raw", "unused"})
private MockedStatic raw;

@Test
public void testNonGeneric() throws Exception {
Class<?> type =
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class
.getDeclaredField("nonGeneric")
.getGenericType(),
"nonGeneric");
assertThat(type).isEqualTo(Void.class);
}

@Test(expected = MockitoException.class)
public void testGeneric() throws Exception {
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class.getDeclaredField("generic").getGenericType(),
"generic");
}

@Test(expected = MockitoException.class)
public void testRaw() throws Exception {
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class.getDeclaredField("raw").getGenericType(), "raw");
}
}
Expand Up @@ -11,15 +11,21 @@

import java.util.concurrent.atomic.AtomicReference;

import org.junit.Rule;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.verification.NoInteractionsWanted;
import org.mockito.exceptions.verification.WantedButNotInvoked;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

public final class StaticMockTest {

@Rule // Adding rule to assert properly managed life-cycle for static mocks
public MockitoRule mockitoRule = MockitoJUnit.rule();

@Test
public void testStaticMockSimple() {
assertEquals("foo", Dummy.foo());
Expand Down
@@ -0,0 +1,25 @@
apply(from = "$rootDir/gradle/dependencies.gradle")

plugins {
java
}

description = "End-to-end tests for automatic registration of MockitoExtension with the inline mock maker."

dependencies {
testCompile(project(":junit-jupiter"))
testCompile(library("assertj"))
testCompile(library("junitPlatformLauncher"))
testCompile(library("junitJupiterApi"))
testRuntime(library("junitJupiterEngine"))
}

tasks.named<Test>("test") {
useJUnitPlatform()
}

val Project.libraries
get() = rootProject.extra["libraries"] as Map<String, Any>

fun Project.library(name: String) =
libraries[name]!!
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2018 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitousage;

import java.util.UUID;

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;

import static org.assertj.core.api.Assertions.*;

class NoExtendsTest {

@Mock
private MockedStatic<UUID> mock;

@Test
void runs() {
mock.when(UUID::randomUUID).thenReturn(new UUID(123, 456));
assertThat(UUID.randomUUID()).isEqualTo(new UUID(123, 456));
}
}
@@ -0,0 +1 @@
org.mockito.junit.jupiter.MockitoExtension
@@ -0,0 +1 @@
junit.jupiter.extensions.autodetection.enabled = true
@@ -0,0 +1 @@
mock-maker-inline

0 comments on commit 9123d1e

Please sign in to comment.