diff --git a/settings.gradle.kts b/settings.gradle.kts index 5ece24a4b3..faef1b8d6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,7 @@ include("deprecatedPluginsTest", "android", "junit-jupiter", "junitJupiterExtensionTest", + "junitJupiterInlineMockMakerExtensionTest", "module-test", "memory-test", "errorprone", @@ -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" diff --git a/src/main/java/org/mockito/MockedStatic.java b/src/main/java/org/mockito/MockedStatic.java index 19ed5e944c..fc23c90776 100644 --- a/src/main/java/org/mockito/MockedStatic.java +++ b/src/main/java/org/mockito/MockedStatic.java @@ -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(); diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index 6ec7a81be9..c212897d59 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -2058,6 +2058,12 @@ public static T spy(Class 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. *

+ * Note: 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. + *

* See examples in javadoc for {@link Mockito} class * * @param classToMock class or interface of which static mocks should be mocked. @@ -2074,6 +2080,12 @@ public static MockedStatic mockStatic(Class 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. *

+ * Note: 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. + *

* See examples in javadoc for {@link Mockito} class * * @param classToMock class or interface of which static mocks should be mocked. @@ -2091,6 +2103,12 @@ public static MockedStatic mockStatic(Class 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. *

+ * Note: 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. + *

* See examples in javadoc for {@link Mockito} class * * @param classToMock class or interface of which static mocks should be mocked. @@ -2108,6 +2126,12 @@ public static MockedStatic mockStatic(Class 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. *

+ * Note: 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. + *

* See examples in javadoc for {@link Mockito} class * * @param classToMock class or interface of which static mocks should be mocked. diff --git a/src/main/java/org/mockito/internal/MockedStaticImpl.java b/src/main/java/org/mockito/internal/MockedStaticImpl.java index 908b810be0..25434769e2 100644 --- a/src/main/java/org/mockito/internal/MockedStaticImpl.java +++ b/src/main/java/org/mockito/internal/MockedStaticImpl.java @@ -157,6 +157,11 @@ public void verifyNoInteractions() { noInteractions().verify(data); } + @Override + public boolean isClosed() { + return closed; + } + @Override public void close() { assertNotClosed(); @@ -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(); + } } diff --git a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java index c7f562a830..48f0515a4b 100644 --- a/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java +++ b/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java @@ -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", - "", - "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", + "", + "as the type parameter. If the type is parameterized, it should be specified as raw type.")); } } diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java index 9bbe9ffee7..89c1ac8622 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineByteBuddyMockMaker.java @@ -382,6 +382,13 @@ public StaticMockControl 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); diff --git a/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java b/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java index dfd5acce9c..549019d135 100644 --- a/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java +++ b/src/main/java/org/mockito/internal/framework/DefaultMockitoSession.java @@ -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(); @@ -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(); } } @@ -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); } } } diff --git a/src/main/java/org/mockito/internal/invocation/finder/AllInvocationsFinder.java b/src/main/java/org/mockito/internal/invocation/finder/AllInvocationsFinder.java index ea9c6d63f4..9b43bbfb1c 100644 --- a/src/main/java/org/mockito/internal/invocation/finder/AllInvocationsFinder.java +++ b/src/main/java/org/mockito/internal/invocation/finder/AllInvocationsFinder.java @@ -42,6 +42,12 @@ public static List find(Iterable mocks) { public static Set findStubbings(Iterable mocks) { Set stubbings = new TreeSet(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 fromSingleMock = new DefaultMockingDetails(mock).getStubbings(); stubbings.addAll(fromSingleMock); diff --git a/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java b/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java new file mode 100644 index 0000000000..906d134d61 --- /dev/null +++ b/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java @@ -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 nonGeneric; + + @SuppressWarnings("unused") + private MockedStatic> 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"); + } +} diff --git a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java index 2dde8d4a9b..230e6773c0 100644 --- a/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java +++ b/subprojects/inline/src/test/java/org/mockitoinline/StaticMockTest.java @@ -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()); diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/junitJupiterInlineMockMakerExtensionTest.gradle.kts b/subprojects/junitJupiterInlineMockMakerExtensionTest/junitJupiterInlineMockMakerExtensionTest.gradle.kts new file mode 100644 index 0000000000..4bf7e47ee9 --- /dev/null +++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/junitJupiterInlineMockMakerExtensionTest.gradle.kts @@ -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") { + useJUnitPlatform() +} + +val Project.libraries + get() = rootProject.extra["libraries"] as Map + +fun Project.library(name: String) = + libraries[name]!! diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java new file mode 100644 index 0000000000..57115fa979 --- /dev/null +++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/java/org/mockitousage/NoExtendsTest.java @@ -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 mock; + + @Test + void runs() { + mock.when(UUID::randomUUID).thenReturn(new UUID(123, 456)); + assertThat(UUID.randomUUID()).isEqualTo(new UUID(123, 456)); + } +} diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 0000000000..02593efe36 --- /dev/null +++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.mockito.junit.jupiter.MockitoExtension diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/junit-platform.properties b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..25ce5c9844 --- /dev/null +++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled = true diff --git a/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/subprojects/junitJupiterInlineMockMakerExtensionTest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline