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

Fixes #1967: Correctly handle mocks with limited life-cycle in listeners. #1968

Merged
merged 1 commit into from Jul 16, 2020
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
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