diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 0000000000..cd85cf642e --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,9 @@ +version: 1 + +update_configs: + - package_manager: "java:gradle" + directory: "/" + update_schedule: "daily" + # Redundant - default repository branch by default + target_branch: "release/2.x" + diff --git a/build.gradle b/build.gradle index 0dac0c72f3..4285ff16ff 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.6' //Using buildscript.classpath so that we can resolve shipkit from maven local, during local testing - classpath 'org.shipkit:shipkit:2.0.28' + classpath 'org.shipkit:shipkit:2.1.6' } } @@ -110,7 +110,7 @@ subprojects { name = rootProject.name + '-' + project.name } } - + afterEvaluate { def lib = publishing.publications.javaLibrary if(lib && !lib.artifactId.startsWith("mockito-")) { diff --git a/doc/release-notes/official.md b/doc/release-notes/official.md index 58570c9b1e..752360b3c8 100644 --- a/doc/release-notes/official.md +++ b/doc/release-notes/official.md @@ -1,5 +1,53 @@ *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* +#### 2.24.10 + - 2019-03-05 - [4 commits](https://github.com/mockito/mockito/compare/v2.24.9...v2.24.10) by [Tim van der Lippe](https://github.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.10-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.10) + - Prevent NPE in findTypeFromGenericInArguments [(#1648)](https://github.com/mockito/mockito/pull/1648) + +#### 2.24.9 + - 2019-03-04 - [12 commits](https://github.com/mockito/mockito/compare/v2.24.7...v2.24.9) by 6 authors - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.9-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.9) + - Commits: [Brice Dutheil](https://github.com/bric3) (5), [Tim van der Lippe](https://github.com/TimvdLippe) (3), [epeee](https://github.com/epeee) (1), [Fr Jeremy Krieg](https://github.com/kriegfrj) (1), [Paweł Pamuła](https://github.com/PawelPamula) (1), shipkit-org (1) + - [Java 9 support] ClassCastExceptions with JDK9 javac [(#357)](https://github.com/mockito/mockito/issues/357) + - [Bugfixes] RETURNS_DEEP_STUBS causes "Raw extraction not supported for : 'null'" in some cases [(#1621)](https://github.com/mockito/mockito/issues/1621) + - Update shipkit plugin (v2.1.6) [(#1647)](https://github.com/mockito/mockito/pull/1647) + - VerificationCollector to handle non-matching args and other assertions [(#1644)](https://github.com/mockito/mockito/pull/1644) + - VerificationCollector doesn't work for invocations with non-matching args [(#1642)](https://github.com/mockito/mockito/issues/1642) + - Fix returns mocks for final classes [(#1641)](https://github.com/mockito/mockito/pull/1641) + - Removes inaccessible links from javadocs in Mockito.java [(#1639)](https://github.com/mockito/mockito/pull/1639) + - Mockito.java contains inaccessible links to articles. [(#1638)](https://github.com/mockito/mockito/issues/1638) + - Handle terminal type var with bounds [(#1624)](https://github.com/mockito/mockito/pull/1624) + - Return null instead of causing a CCE [(#1612)](https://github.com/mockito/mockito/pull/1612) + +#### 2.24.7 + - 2019-02-28 - [2 commits](https://github.com/mockito/mockito/compare/v2.24.6...v2.24.7) by [shipkit-org](https://github.com/shipkit-org) (1), [Tim van der Lippe](https://github.com/TimvdLippe) (1) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.7-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.7) + - Fix handling of generics in ReturnsMocks [(#1635)](https://github.com/mockito/mockito/pull/1635) + +#### 2.24.6 + - 2019-02-27 - [16 commits](https://github.com/mockito/mockito/compare/v2.24.5...v2.24.6) by [Szczepan Faber](https://github.com/mockitoguy) (15), [Marcin Stachniuk](https://github.com/mstachniuk) (1) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.6-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.6) + - Make use of Shipkit v2.1.3 [(#1626)](https://github.com/mockito/mockito/pull/1626) + - Exposed new API - StubbingLookupListener [(#1543)](https://github.com/mockito/mockito/pull/1543) + +#### 2.24.5 + - 2019-02-18 - [2 commits](https://github.com/mockito/mockito/compare/v2.24.4...v2.24.5) by [Tim van der Lippe](https://github.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.5-green.svg)](https://bintray.com/mockito/maven/mockito/2.24.5) + - No pull requests referenced in commit messages. + +#### 2.24.4 + - 2019-02-13 - [1 commit](https://github.com/mockito/mockito/compare/v2.24.3...v2.24.4) by [Alex Simkin](https://github.com/SimY4) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.4-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.4) + - Fixes #1618 : Fix strict stubbing profile serialization support. [(#1620)](https://github.com/mockito/mockito/pull/1620) + - Serializable flag doesn't make mock serializable [(#1618)](https://github.com/mockito/mockito/issues/1618) + +#### 2.24.3 + - 2019-02-12 - [2 commits](https://github.com/mockito/mockito/compare/v2.24.2...v2.24.3) by [Marcin Zajączkowski](https://github.com/szpak) (1), [Tim van der Lippe](https://github.com/TimvdLippe) (1) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.3-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.3) + - [Java 9 support] ClassCastExceptions with JDK9 javac [(#357)](https://github.com/mockito/mockito/issues/357) + - Return null instead of causing a CCE [(#1612)](https://github.com/mockito/mockito/pull/1612) + - Automatic dependency update with Dependabot [(#1600)](https://github.com/mockito/mockito/pull/1600) + - Fix/bug 1551 cce on smart not null answers [(#1576)](https://github.com/mockito/mockito/pull/1576) + +#### 2.24.2 + - 2019-02-11 - [1 commit](https://github.com/mockito/mockito/compare/v2.24.1...v2.24.2) by [Tim van der Lippe](https://github.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.2-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.2) + - Fix issue with mocking of java.util.* classes [(#1617)](https://github.com/mockito/mockito/pull/1617) + - Issue with mocking type in "java.util.*", Java 12 [(#1615)](https://github.com/mockito/mockito/issues/1615) + #### 2.24.1 - 2019-02-04 - [1 commit](https://github.com/mockito/mockito/compare/v2.24.0...v2.24.1) by [zoujinhe](https://github.com/zoujinhe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.24.1-green.svg)](https://bintray.com/mockito/maven/mockito-development/2.24.1) - typo? ... 'thenReturn' instruction if completed -> ... 'thenReturn' instruction is completed [(#1608)](https://github.com/mockito/mockito/pull/1608) diff --git a/gradle/errorprone.gradle b/gradle/errorprone.gradle index 1d9cf7428e..43c8633fcd 100644 --- a/gradle/errorprone.gradle +++ b/gradle/errorprone.gradle @@ -10,3 +10,7 @@ if (JavaVersion.current() == JavaVersion.VERSION_1_8) { dependencies { errorprone libraries.errorprone } + +tasks.named("compileTestJava").configure { + options.errorprone.errorproneArgs << "-Xep:MockitoCast:OFF" +} diff --git a/src/main/java/org/mockito/MockSettings.java b/src/main/java/org/mockito/MockSettings.java index c79c243175..95fcfe3236 100644 --- a/src/main/java/org/mockito/MockSettings.java +++ b/src/main/java/org/mockito/MockSettings.java @@ -9,6 +9,7 @@ import org.mockito.invocation.InvocationFactory; import org.mockito.invocation.MockHandler; import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.listeners.VerificationStartedListener; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.SerializableMode; @@ -203,6 +204,24 @@ public interface MockSettings extends Serializable { */ MockSettings verboseLogging(); + /** + * Add stubbing lookup listener to the mock object. + * + * Multiple listeners may be added and they will be notified orderly. + * + * For use cases and more info see {@link StubbingLookupListener}. + * + * Example: + *

+     *  List mockWithListener = mock(List.class, withSettings().stubbingLookupListeners(new YourStubbingLookupListener()));
+     * 
+ * + * @param listeners The stubbing lookup listeners to add. May not be null. + * @return settings instance so that you can fluently specify other settings + * @since 2.24.6 + */ + MockSettings stubbingLookupListeners(StubbingLookupListener... listeners); + /** * Registers a listener for method invocations on this mock. The listener is * notified every time a method on this mock is called. diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index 2e10eca426..5066eec3d7 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -208,7 +208,7 @@ * * //Although it is possible to verify a stubbed invocation, usually it's just redundant * //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed). - * //If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here. + * //If your code doesn't care what get(0) returns, then it should not be stubbed. * verify(mockedList).get(0); * * @@ -427,8 +427,7 @@ * Some users who did a lot of classic, expect-run-verify mocking tend to use verifyNoMoreInteractions() very often, even in every test method. * verifyNoMoreInteractions() is not recommended to use in every test method. * verifyNoMoreInteractions() is a handy assertion from the interaction testing toolkit. Use it only when it's relevant. - * Abusing it leads to overspecified, less maintainable tests. You can find further reading - * here. + * Abusing it leads to overspecified, less maintainable tests. * *

* See also {@link Mockito#never()} - it is more explicit and @@ -599,8 +598,7 @@ * Before the release 1.8, Mockito spies were not real partial mocks. * The reason was we thought partial mock is a code smell. * At some point we found legitimate use cases for partial mocks - * (3rd party interfaces, interim refactoring of legacy code, the full article is - * here) + * (3rd party interfaces, interim refactoring of legacy code). *

* *


@@ -709,8 +707,7 @@
  * 

16. Real partial mocks (Since 1.8.0)

* * Finally, after many internal debates & discussions on the mailing list, partial mock support was added to Mockito. - * Previously we considered partial mocks as code smells. However, we found a legitimate use case for partial mocks - more reading: - * here + * Previously we considered partial mocks as code smells. However, we found a legitimate use case for partial mocks. *

* Before release 1.8 spy() was not producing real partial mocks and it was confusing for some users. * Read more about spying: here or in javadoc for {@link Mockito#spy(Object)} method. @@ -1695,6 +1692,7 @@ public class Mockito extends ArgumentMatchers { /** * Optional Answer to be used with {@link Mockito#mock(Class, Answer)} + * *

* {@link Answer} can be used to define the return values of unstubbed invocations. *

@@ -1726,8 +1724,11 @@ public class Mockito extends ArgumentMatchers { *

* *

- * Note: Stubbing partial mocks using when(mock.getSomething()).thenReturn(fakeValue) + * Note 1: Stubbing partial mocks using when(mock.getSomething()).thenReturn(fakeValue) * syntax will call the real method. For partial mock it's recommended to use doReturn syntax. + *

+ * Note 2: If the mock is serialized then deserialized, then this answer will not be able to understand + * generics metadata. */ public static final Answer CALLS_REAL_METHODS = Answers.CALLS_REAL_METHODS; @@ -2080,7 +2081,6 @@ public static T spy(Class classToSpy) { * Let's say you've stubbed foo.bar(). * If your code cares what foo.bar() returns then something else breaks(often before even verify() gets executed). * If your code doesn't care what get(0) returns then it should not be stubbed. - * Not convinced? See here. * *

* See examples in javadoc for {@link Mockito} class @@ -2112,7 +2112,6 @@ public static OngoingStubbing when(T methodCall) { * Let's say you've stubbed foo.bar(). * If your code cares what foo.bar() returns then something else breaks(often before even verify() gets executed). * If your code doesn't care what get(0) returns then it should not be stubbed. - * Not convinced? See here. * *

* See examples in javadoc for {@link Mockito} class @@ -2214,8 +2213,7 @@ public static void clearInvocations(T ... mocks) { * Some users who did a lot of classic, expect-run-verify mocking tend to use verifyNoMoreInteractions() very often, even in every test method. * verifyNoMoreInteractions() is not recommended to use in every test method. * verifyNoMoreInteractions() is a handy assertion from the interaction testing toolkit. Use it only when it's relevant. - * Abusing it leads to overspecified, less maintainable tests. You can find further reading - * here. + * Abusing it leads to overspecified, less maintainable tests. *

* This method will also detect unverified invocations that occurred before the test method, * for example: in setUp(), @Before method or in constructor. diff --git a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java index ca67730793..50134b4458 100644 --- a/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java +++ b/src/main/java/org/mockito/internal/creation/MockSettingsImpl.java @@ -11,6 +11,7 @@ import org.mockito.internal.util.MockCreationValidator; import org.mockito.internal.util.MockNameImpl; import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.listeners.VerificationStartedListener; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.MockName; @@ -19,17 +20,17 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import static java.util.Arrays.asList; import static org.mockito.internal.exceptions.Reporter.defaultAnswerDoesNotAcceptNullParameter; import static org.mockito.internal.exceptions.Reporter.extraInterfacesAcceptsOnlyInterfaces; import static org.mockito.internal.exceptions.Reporter.extraInterfacesDoesNotAcceptNullParameters; import static org.mockito.internal.exceptions.Reporter.extraInterfacesRequiresAtLeastOneInterface; -import static org.mockito.internal.exceptions.Reporter.invocationListenersRequiresAtLeastOneListener; import static org.mockito.internal.exceptions.Reporter.methodDoesNotAcceptParameter; +import static org.mockito.internal.exceptions.Reporter.requiresAtLeastOneListener; import static org.mockito.internal.util.collections.Sets.newSet; @SuppressWarnings("unchecked") @@ -154,7 +155,7 @@ public Object[] getConstructorArgs() { } List resultArgs = new ArrayList(constructorArgs.length + 1); resultArgs.add(outerClassInstance); - resultArgs.addAll(Arrays.asList(constructorArgs)); + resultArgs.addAll(asList(constructorArgs)); return resultArgs.toArray(new Object[constructorArgs.length + 1]); } @@ -173,17 +174,23 @@ public MockSettings verboseLogging() { @Override public MockSettings invocationListeners(InvocationListener... listeners) { - if (listeners == null || listeners.length == 0) { - throw invocationListenersRequiresAtLeastOneListener(); - } addListeners(listeners, invocationListeners, "invocationListeners"); return this; } - private static void addListeners(T[] listeners, List container, String method) { + @Override + public MockSettings stubbingLookupListeners(StubbingLookupListener... listeners) { + addListeners(listeners, stubbingLookupListeners, "stubbingLookupListeners"); + return this; + } + + static void addListeners(T[] listeners, List container, String method) { if (listeners == null) { throw methodDoesNotAcceptParameter(method, "null vararg array."); } + if (listeners.length == 0) { + throw requiresAtLeastOneListener(method); + } for (T listener : listeners) { if (listener == null) { throw methodDoesNotAcceptParameter(method, "null listeners."); @@ -207,13 +214,8 @@ private boolean invocationListenersContainsType(Class clazz) { return false; } - @Override - public List getInvocationListeners() { - return this.invocationListeners; - } - public boolean hasInvocationListeners() { - return !invocationListeners.isEmpty(); + return !getInvocationListeners().isEmpty(); } @Override diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java index 906692768d..e57a82e739 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodInterceptor.java @@ -46,13 +46,11 @@ Object doIntercept(Object mock, Method invokedMethod, Object[] arguments, RealMethod realMethod) throws Throwable { - return doIntercept( - mock, - invokedMethod, - arguments, - realMethod, - new LocationImpl() - ); + return doIntercept(mock, + invokedMethod, + arguments, + realMethod, + new LocationImpl()); } Object doIntercept(Object mock, @@ -108,11 +106,11 @@ public static Object interceptSuperCallable(@This Object mock, return superCall.call(); } return interceptor.doIntercept( - mock, - invokedMethod, - arguments, - new RealMethod.FromCallable(superCall) - ); + mock, + invokedMethod, + arguments, + new RealMethod.FromCallable(superCall) + ); } @SuppressWarnings("unused") @@ -126,11 +124,11 @@ public static Object interceptAbstract(@This Object mock, return stubValue; } return interceptor.doIntercept( - mock, - invokedMethod, - arguments, - RealMethod.IsIllegal.INSTANCE - ); + mock, + invokedMethod, + arguments, + RealMethod.IsIllegal.INSTANCE + ); } } } diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java index 8968b9e8e4..093978dd22 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -98,7 +98,7 @@ public Class mockClass(MockFeatures features) { if (localMock || loader instanceof MultipleParentClassLoader && !isComingFromJDK(features.mockedType)) { typeName = features.mockedType.getName(); } else { - typeName = InjectionBase.class.getPackage().getName() + features.mockedType.getSimpleName(); + typeName = InjectionBase.class.getPackage().getName() + "." + features.mockedType.getSimpleName(); } String name = String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); diff --git a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java index 81f52f982b..3b45592c72 100644 --- a/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java +++ b/src/main/java/org/mockito/internal/creation/settings/CreationSettings.java @@ -4,8 +4,8 @@ */ package org.mockito.internal.creation.settings; -import org.mockito.internal.listeners.StubbingLookupListener; import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.listeners.VerificationStartedListener; import org.mockito.mock.MockCreationSettings; import org.mockito.mock.MockName; @@ -18,6 +18,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; public class CreationSettings implements MockCreationSettings, Serializable { private static final long serialVersionUID = -6789800638070123629L; @@ -30,7 +31,11 @@ public class CreationSettings implements MockCreationSettings, Serializabl protected MockName mockName; protected SerializableMode serializableMode = SerializableMode.NONE; protected List invocationListeners = new ArrayList(); - protected final List stubbingLookupListeners = new ArrayList(); + + //Other listeners in this class may also need concurrency-safe implementation. However, no issue was reported about it. + // If we do it, we need to understand usage patterns and choose the right concurrent implementation. + protected List stubbingLookupListeners = new CopyOnWriteArrayList(); + protected List verificationStartedListeners = new LinkedList(); protected boolean stubOnly; protected boolean stripAnnotations; @@ -43,6 +48,7 @@ public CreationSettings() {} @SuppressWarnings("unchecked") public CreationSettings(CreationSettings copy) { + //TODO can we have a reflection test here? We had a couple of bugs here in the past. this.typeToMock = copy.typeToMock; this.extraInterfaces = copy.extraInterfaces; this.name = copy.name; @@ -51,6 +57,7 @@ public CreationSettings(CreationSettings copy) { this.mockName = copy.mockName; this.serializableMode = copy.serializableMode; this.invocationListeners = copy.invocationListeners; + this.stubbingLookupListeners = copy.stubbingLookupListeners; this.verificationStartedListeners = copy.verificationStartedListeners; this.stubOnly = copy.stubOnly; this.useConstructor = copy.isUsingConstructor(); diff --git a/src/main/java/org/mockito/internal/exceptions/Reporter.java b/src/main/java/org/mockito/internal/exceptions/Reporter.java index 3158228f22..1b68fbb657 100644 --- a/src/main/java/org/mockito/internal/exceptions/Reporter.java +++ b/src/main/java/org/mockito/internal/exceptions/Reporter.java @@ -684,8 +684,8 @@ public static MockitoException methodDoesNotAcceptParameter(String method, Strin return new MockitoException(method + "() does not accept " + parameter + " See the Javadoc."); } - public static MockitoException invocationListenersRequiresAtLeastOneListener() { - return new MockitoException("invocationListeners() requires at least one listener"); + public static MockitoException requiresAtLeastOneListener(String method) { + return new MockitoException(method + "() requires at least one listener"); } public static MockitoException invocationListenerThrewException(InvocationListener listener, Throwable listenerThrowable) { diff --git a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java index 21212a6754..b5d2022b39 100644 --- a/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java +++ b/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java @@ -5,13 +5,14 @@ package org.mockito.internal.junit; import org.mockito.internal.exceptions.Reporter; -import org.mockito.internal.listeners.StubbingLookupEvent; -import org.mockito.internal.listeners.StubbingLookupListener; import org.mockito.internal.stubbing.UnusedStubbingReporting; import org.mockito.invocation.Invocation; +import org.mockito.listeners.StubbingLookupEvent; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.quality.Strictness; import org.mockito.stubbing.Stubbing; +import java.io.Serializable; import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -22,7 +23,9 @@ * Default implementation of stubbing lookup listener. * Fails early if stub called with unexpected arguments, but only if current strictness is set to STRICT_STUBS. */ -class DefaultStubbingLookupListener implements StubbingLookupListener { +class DefaultStubbingLookupListener implements StubbingLookupListener, Serializable { + + private static final long serialVersionUID = -6789800638070123629L; private Strictness currentStrictness; private boolean mismatchesReported; diff --git a/src/main/java/org/mockito/internal/junit/StrictStubsRunnerTestListener.java b/src/main/java/org/mockito/internal/junit/StrictStubsRunnerTestListener.java index 9e60c31356..d23431bae1 100644 --- a/src/main/java/org/mockito/internal/junit/StrictStubsRunnerTestListener.java +++ b/src/main/java/org/mockito/internal/junit/StrictStubsRunnerTestListener.java @@ -4,7 +4,6 @@ */ package org.mockito.internal.junit; -import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.mock.MockCreationSettings; import org.mockito.quality.Strictness; @@ -22,8 +21,8 @@ public void testFinished(TestFinishedEvent event) {} public void onMockCreated(Object mock, MockCreationSettings settings) { //It is not ideal that we modify the state of MockCreationSettings object //MockCreationSettings is intended to be an immutable view of the creation settings - //In future, we should start passing MockSettings object to the creation listener - //TODO #793 - when completed, we should be able to get rid of the CreationSettings casting below - ((CreationSettings) settings).getStubbingLookupListeners().add(stubbingLookupListener); + //However, we our previous listeners work this way and it hasn't backfired. + //Since it is simple and pragmatic, we'll keep it for now. + settings.getStubbingLookupListeners().add(stubbingLookupListener); } } diff --git a/src/main/java/org/mockito/internal/junit/VerificationCollectorImpl.java b/src/main/java/org/mockito/internal/junit/VerificationCollectorImpl.java index 7c19d35652..e54d47864e 100644 --- a/src/main/java/org/mockito/internal/junit/VerificationCollectorImpl.java +++ b/src/main/java/org/mockito/internal/junit/VerificationCollectorImpl.java @@ -75,7 +75,7 @@ private void append(String message) { this.numberOfFailures++; this.builder.append('\n') .append(this.numberOfFailures).append(". ") - .append(message.substring(1, message.length())); + .append(message.trim()).append('\n'); } private class VerificationWrapper implements VerificationMode { @@ -89,7 +89,7 @@ private VerificationWrapper(VerificationMode delegate) { public void verify(VerificationData data) { try { this.delegate.verify(data); - } catch (MockitoAssertionError error) { + } catch (AssertionError error) { VerificationCollectorImpl.this.append(error.getMessage()); } } diff --git a/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java b/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java deleted file mode 100644 index 6fa37a159d..0000000000 --- a/src/main/java/org/mockito/internal/listeners/StubbingLookupListener.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2017 Mockito contributors - * This program is made available under the terms of the MIT License. - */ -package org.mockito.internal.listeners; - -/** - * Listens to attempts to look up stubbing answer for given mocks. This class is internal for now. - *

- * How does it work? - * When method is called on the mock object, Mockito looks for any answer (stubbing) declared on that mock. - * If the stubbed answer is found, that answer is invoked (value returned, thrown exception, etc.). - * If the answer is not found (e.g. that invocation was not stubbed on the mock), mock's default answer is used. - * This listener implementation is notified when Mockito looked up an answer for invocation on a mock. - *

- * If we make this interface a part of public API (and we should): - * - make the implementation unified with InvocationListener (for example: common parent, marker interface MockObjectListener - * single method for adding listeners so long they inherit from the parent) - * - make the error handling strict - * so that Mockito provides decent message when listener fails due to poor implementation. - */ -public interface StubbingLookupListener { - - /** - * Called by the framework when Mockito looked up an answer for invocation on a mock. - * - * @param stubbingLookupEvent - Information about the looked up stubbing - */ - void onStubbingLookup(StubbingLookupEvent stubbingLookupEvent); -} diff --git a/src/main/java/org/mockito/internal/listeners/StubbingLookupNotifier.java b/src/main/java/org/mockito/internal/listeners/StubbingLookupNotifier.java index 3e550013a3..a0de0c37b5 100644 --- a/src/main/java/org/mockito/internal/listeners/StubbingLookupNotifier.java +++ b/src/main/java/org/mockito/internal/listeners/StubbingLookupNotifier.java @@ -6,6 +6,8 @@ import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.invocation.Invocation; +import org.mockito.listeners.StubbingLookupEvent; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Stubbing; diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java new file mode 100644 index 0000000000..979c8f7813 --- /dev/null +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/RetrieveGenericsForDefaultAnswers.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing.defaultanswers; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import org.mockito.internal.MockitoCore; +import org.mockito.internal.util.MockUtil; +import org.mockito.internal.util.reflection.GenericMetadataSupport; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.mock.MockCreationSettings; + +class RetrieveGenericsForDefaultAnswers { + + private static final MockitoCore MOCKITO_CORE = new MockitoCore(); + + static Object returnTypeForMockWithCorrectGenerics( + InvocationOnMock invocation, AnswerCallback answerCallback) { + Class type = invocation.getMethod().getReturnType(); + + final Type returnType = invocation.getMethod().getGenericReturnType(); + + Object defaultReturnValue = null; + + if (returnType instanceof TypeVariable) { + type = findTypeFromGeneric(invocation, (TypeVariable) returnType); + if (type != null) { + defaultReturnValue = delegateChains(type); + } + } + + if (defaultReturnValue != null) { + return defaultReturnValue; + } + + if (type != null) { + if (!MOCKITO_CORE.isTypeMockable(type)) { + return null; + } + + return answerCallback.apply(type); + } + + return answerCallback.apply(null); + } + + /** + * Try to resolve the result value using {@link ReturnsEmptyValues} and {@link ReturnsMoreEmptyValues}. + * + * This will try to use all parent class (superclass & interfaces) to retrieve the value.. + * + * @param type the return type of the method + * @return a non-null instance if the type has been resolve. Null otherwise. + */ + private static Object delegateChains(final Class type) { + final ReturnsEmptyValues returnsEmptyValues = new ReturnsEmptyValues(); + Object result = returnsEmptyValues.returnValueFor(type); + + if (result == null) { + Class emptyValueForClass = type; + while (emptyValueForClass != null && result == null) { + final Class[] classes = emptyValueForClass.getInterfaces(); + for (Class clazz : classes) { + result = returnsEmptyValues.returnValueFor(clazz); + if (result != null) { + break; + } + } + emptyValueForClass = emptyValueForClass.getSuperclass(); + } + } + + if (result == null) { + result = new ReturnsMoreEmptyValues().returnValueFor(type); + } + + return result; + } + + /** + * Retrieve the expected type when it came from a primitive. If the type cannot be retrieve, return null. + * + * @param invocation the current invocation + * @param returnType the expected return type + * @return the type or null if not found + */ + private static Class findTypeFromGeneric(final InvocationOnMock invocation, final TypeVariable returnType) { + // Class level + final MockCreationSettings mockSettings = MockUtil.getMockHandler(invocation.getMock()).getMockSettings(); + final GenericMetadataSupport returnTypeSupport = GenericMetadataSupport + .inferFrom(mockSettings.getTypeToMock()) + .resolveGenericReturnType(invocation.getMethod()); + final Class rawType = returnTypeSupport.rawType(); + + // Method level + if (rawType == Object.class) { + return findTypeFromGenericInArguments(invocation, returnType); + } + return rawType; + } + + /** + * Find a return type using generic arguments provided by the calling method. + * + * @param invocation the current invocation + * @param returnType the expected return type + * @return the return type or null if the return type cannot be found + */ + private static Class findTypeFromGenericInArguments(final InvocationOnMock invocation, final TypeVariable returnType) { + final Type[] parameterTypes = invocation.getMethod().getGenericParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + Type argType = parameterTypes[i]; + if (returnType.equals(argType)) { + Object argument = invocation.getArgument(i); + + if (argument == null) { + return null; + } + + return argument.getClass(); + } + if (argType instanceof GenericArrayType) { + argType = ((GenericArrayType) argType).getGenericComponentType(); + if (returnType.equals(argType)) { + return invocation.getArgument(i).getClass(); + } + } + } + return null; + } + + interface AnswerCallback { + Object apply(Class type); + } +} diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java index 3909ff041c..356b2e629f 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java @@ -25,7 +25,7 @@ /** * Returning deep stub implementation. * - * Will return previously created mock if the invocation matches. + *

Will return previously created mock if the invocation matches. * *

Supports nested generic information, with this answer you can write code like this : * @@ -37,6 +37,8 @@ * *

* + *

However this answer does not support generics information when the mock has been deserialized. + * * @see org.mockito.Mockito#RETURNS_DEEP_STUBS * @see org.mockito.Answers#RETURNS_DEEP_STUBS */ @@ -46,13 +48,22 @@ public class ReturnsDeepStubs implements Answer, Serializable { public Object answer(InvocationOnMock invocation) throws Throwable { GenericMetadataSupport returnTypeGenericMetadata = - actualParameterizedType(invocation.getMock()).resolveGenericReturnType(invocation.getMethod()); + actualParameterizedType(invocation.getMock()).resolveGenericReturnType(invocation.getMethod()); Class rawType = returnTypeGenericMetadata.rawType(); if (!mockitoCore().isTypeMockable(rawType)) { return delegate().returnValueFor(rawType); } + // When dealing with erased generics, we only receive the Object type as rawType. At this + // point, there is nothing to salvage for Mockito. Instead of trying to be smart and generate + // a mock that would potentially match the return signature, instead return `null`. This + // is valid per the CheckCast JVM instruction and is better than causing a ClassCastException + // on runtime. + if (rawType.equals(Object.class) && !returnTypeGenericMetadata.hasRawExtraInterfaces()) { + return null; + } + return deepStub(invocation, returnTypeGenericMetadata); } @@ -69,9 +80,9 @@ private Object deepStub(InvocationOnMock invocation, GenericMetadataSupport retu // record deep stub answer StubbedInvocationMatcher stubbing = recordDeepStubAnswer( - newDeepStubMock(returnTypeGenericMetadata, invocation.getMock()), - container - ); + newDeepStubMock(returnTypeGenericMetadata, invocation.getMock()), + container + ); // deep stubbing creates a stubbing and immediately uses it // so the stubbing is actually used by the same invocation @@ -88,24 +99,24 @@ private Object deepStub(InvocationOnMock invocation, GenericMetadataSupport retu * {@link ReturnsDeepStubs} answer in which we will store the returned type generic metadata. * * @param returnTypeGenericMetadata The metadata to use to create the new mock. - * @param parentMock The parent of the current deep stub mock. + * @param parentMock The parent of the current deep stub mock. * @return The mock */ private Object newDeepStubMock(GenericMetadataSupport returnTypeGenericMetadata, Object parentMock) { MockCreationSettings parentMockSettings = MockUtil.getMockSettings(parentMock); return mockitoCore().mock( - returnTypeGenericMetadata.rawType(), - withSettingsUsing(returnTypeGenericMetadata, parentMockSettings) - ); + returnTypeGenericMetadata.rawType(), + withSettingsUsing(returnTypeGenericMetadata, parentMockSettings) + ); } private MockSettings withSettingsUsing(GenericMetadataSupport returnTypeGenericMetadata, MockCreationSettings parentMockSettings) { MockSettings mockSettings = returnTypeGenericMetadata.hasRawExtraInterfaces() ? - withSettings().extraInterfaces(returnTypeGenericMetadata.rawExtraInterfaces()) - : withSettings(); + withSettings().extraInterfaces(returnTypeGenericMetadata.rawExtraInterfaces()) + : withSettings(); return propagateSerializationSettings(mockSettings, parentMockSettings) - .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata)); + .defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata)); } private MockSettings propagateSerializationSettings(MockSettings mockSettings, MockCreationSettings parentMockSettings) { @@ -139,6 +150,15 @@ public ReturnsDeepStubsSerializationFallback(GenericMetadataSupport returnTypeGe protected GenericMetadataSupport actualParameterizedType(Object mock) { return returnTypeGenericMetadata; } + + /** + * Generics support and serialization with deep stubs don't work together. + *

+ * The issue is that GenericMetadataSupport is not serializable because + * the type elements inferred via reflection are not serializable. Supporting + * serialization would require to replace all types coming from the Java reflection + * with our own and still managing type equality with the JDK ones. + */ private Object writeReplace() throws IOException { return Mockito.RETURNS_DEEP_STUBS; } @@ -152,6 +172,7 @@ private static class DeeplyStubbedAnswer implements Answer, Serializable DeeplyStubbedAnswer(Object mock) { this.mock = mock; } + public Object answer(InvocationOnMock invocation) throws Throwable { return mock; } diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java index 302e4df092..5d4befe60e 100755 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocks.java @@ -4,33 +4,36 @@ */ package org.mockito.internal.stubbing.defaultanswers; -import org.mockito.internal.MockitoCore; +import java.io.Serializable; +import org.mockito.Mockito; import org.mockito.internal.creation.MockSettingsImpl; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.io.Serializable; - public class ReturnsMocks implements Answer, Serializable { private static final long serialVersionUID = -6755257986994634579L; - private final MockitoCore mockitoCore = new MockitoCore(); private final Answer delegate = new ReturnsMoreEmptyValues(); - public Object answer(InvocationOnMock invocation) throws Throwable { - Object ret = delegate.answer(invocation); - if (ret != null) { - return ret; - } - - return returnValueFor(invocation.getMethod().getReturnType()); - } + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable { + Object defaultReturnValue = delegate.answer(invocation); - Object returnValueFor(Class clazz) { - if (!mockitoCore.isTypeMockable(clazz)) { - return null; + if (defaultReturnValue != null) { + return defaultReturnValue; } - return mockitoCore.mock(clazz, new MockSettingsImpl().defaultAnswer(this)); + return RetrieveGenericsForDefaultAnswers.returnTypeForMockWithCorrectGenerics(invocation, + new RetrieveGenericsForDefaultAnswers.AnswerCallback() { + @Override + public Object apply(Class type) { + if (type == null) { + type = invocation.getMethod().getReturnType(); + } + + return Mockito + .mock(type, new MockSettingsImpl().defaultAnswer(ReturnsMocks.this)); + } + }); } } diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java index ff6b7942d1..6cf50c7d39 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsSmartNulls.java @@ -8,18 +8,10 @@ import static org.mockito.internal.util.ObjectMethodsGuru.isToStringMethod; import java.io.Serializable; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; - import org.mockito.Mockito; import org.mockito.internal.debugging.LocationImpl; -import org.mockito.internal.util.MockUtil; -import org.mockito.internal.util.reflection.GenericMetadataSupport; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.Location; -import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Answer; /** @@ -46,108 +38,25 @@ public class ReturnsSmartNulls implements Answer, Serializable { private final Answer delegate = new ReturnsMoreEmptyValues(); + @Override public Object answer(final InvocationOnMock invocation) throws Throwable { Object defaultReturnValue = delegate.answer(invocation); - if (defaultReturnValue != null) { - return defaultReturnValue; - } - Class type = invocation.getMethod().getReturnType(); - final Type returnType = invocation.getMethod().getGenericReturnType(); - if (returnType instanceof TypeVariable) { - type = findTypeFromGeneric(invocation, (TypeVariable) returnType); - if (type != null) { - defaultReturnValue = delegateChains(type); - } - } if (defaultReturnValue != null) { return defaultReturnValue; } - if (type != null && !type.isPrimitive() && !Modifier.isFinal(type.getModifiers())) { - final Location location = new LocationImpl(); - return Mockito.mock(type, new ThrowsSmartNullPointer(invocation, location)); - } - return null; - } - - /** - * Try to resolve the result value using {@link ReturnsEmptyValues} and {@link ReturnsMoreEmptyValues}. - * - * This will try to use all parent class (superclass & interfaces) to retrieve the value.. - * - * @param type the return type of the method - * @return a non-null instance if the type has been resolve. Null otherwise. - */ - private Object delegateChains(final Class type) { - final ReturnsEmptyValues returnsEmptyValues = new ReturnsEmptyValues(); - Object result = returnsEmptyValues.returnValueFor(type); - - if (result == null) { - Class emptyValueForClass = type; - while (emptyValueForClass != null && result == null) { - final Class[] classes = emptyValueForClass.getInterfaces(); - for (Class clazz : classes) { - result = returnsEmptyValues.returnValueFor(clazz); - if (result != null) { - break; + return RetrieveGenericsForDefaultAnswers.returnTypeForMockWithCorrectGenerics(invocation, + new RetrieveGenericsForDefaultAnswers.AnswerCallback() { + @Override + public Object apply(Class type) { + if (type == null) { + return null; } - } - emptyValueForClass = emptyValueForClass.getSuperclass(); - } - } - - if (result == null) { - result = new ReturnsMoreEmptyValues().returnValueFor(type); - } - - return result; - } - - /** - * Retrieve the expected type when it came from a primitive. If the type cannot be retrieve, return null. - * - * @param invocation the current invocation - * @param returnType the expected return type - * @return the type or null if not found - */ - private Class findTypeFromGeneric(final InvocationOnMock invocation, final TypeVariable returnType) { - // Class level - final MockCreationSettings mockSettings = MockUtil.getMockHandler(invocation.getMock()).getMockSettings(); - final GenericMetadataSupport returnTypeSupport = GenericMetadataSupport - .inferFrom(mockSettings.getTypeToMock()) - .resolveGenericReturnType(invocation.getMethod()); - final Class rawType = returnTypeSupport.rawType(); - // Method level - if (rawType == Object.class) { - return findTypeFromGenericInArguments(invocation, returnType); - } - return rawType; - } - - /** - * Find a return type using generic arguments provided by the calling method. - * - * @param invocation the current invocation - * @param returnType the expected return type - * @return the return type or null if the return type cannot be found - */ - private Class findTypeFromGenericInArguments(final InvocationOnMock invocation, final TypeVariable returnType) { - final Type[] parameterTypes = invocation.getMethod().getGenericParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - Type argType = parameterTypes[i]; - if (returnType.equals(argType)) { - return invocation.getArgument(i).getClass(); - } - if (argType instanceof GenericArrayType) { - argType = ((GenericArrayType) argType).getGenericComponentType(); - if (returnType.equals(argType)) { - return invocation.getArgument(i).getClass(); + return Mockito.mock(type, new ThrowsSmartNullPointer(invocation, new LocationImpl())); } - } - } - return null; + }); } private static class ThrowsSmartNullPointer implements Answer { @@ -156,7 +65,7 @@ private static class ThrowsSmartNullPointer implements Answer { private final Location location; - public ThrowsSmartNullPointer(InvocationOnMock unstubbedInvocation, Location location) { + ThrowsSmartNullPointer(InvocationOnMock unstubbedInvocation, Location location) { this.unstubbedInvocation = unstubbedInvocation; this.location = location; } diff --git a/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java b/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java index 596827c64c..80cbf65be3 100644 --- a/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java +++ b/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java @@ -8,8 +8,23 @@ import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.util.Checks; -import java.lang.reflect.*; -import java.util.*; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; /** @@ -17,20 +32,20 @@ * and accessible members. * *

- * The main idea of this code is to create a Map that will help to resolve return types. - * In order to actually work with nested generics, this map will have to be passed along new instances - * as a type context. + * The main idea of this code is to create a Map that will help to resolve return types. + * In order to actually work with nested generics, this map will have to be passed along new instances + * as a type context. *

* *

- * Hence : - *

    - *
  • A new instance representing the metadata is created using the {@link #inferFrom(Type)} method from a real - * Class or from a ParameterizedType, other types are not yet supported.
  • + * Hence : + *
      + *
    • A new instance representing the metadata is created using the {@link #inferFrom(Type)} method from a real + * Class or from a ParameterizedType, other types are not yet supported.
    • * - *
    • Then from this metadata, we can extract meta-data for a generic return type of a method, using - * {@link #resolveGenericReturnType(Method)}.
    • - *
    + *
  • Then from this metadata, we can extract meta-data for a generic return type of a method, using + * {@link #resolveGenericReturnType(Method)}.
  • + *
*

* *

@@ -97,8 +112,9 @@ protected Class extractRawTypeOf(Type type) { } if (type instanceof TypeVariable) { /* - * If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared - * on the class definition, such as such as List. + * If type is a TypeVariable, then it is needed to gather data elsewhere. + * Usually TypeVariables are declared on the class definition, such as such + * as List. */ return extractRawTypeOf(contextualActualTypeParameters.get(type)); } @@ -116,10 +132,21 @@ protected void registerTypeVariablesOn(Type classType) { TypeVariable typeParameter = typeParameters[i]; Type actualTypeArgument = actualTypeArguments[i]; - // Prevent registration of a cycle of TypeVariables. This can happen when we are processing - // type parameters in a Method, while we already processed the type parameters of a class. - if (actualTypeArgument instanceof TypeVariable && contextualActualTypeParameters.containsKey(typeParameter)) { - continue; + if (actualTypeArgument instanceof TypeVariable) { + /* + * If actualTypeArgument is a TypeVariable, and it is not present in + * the context map then it is needed to try harder to gather more data + * from the type argument itself. In some case the type argument do + * define upper bounds, this allow to look for them if not in the + * context map. + */ + registerTypeVariableIfNotPresent((TypeVariable) actualTypeArgument); + + // Prevent registration of a cycle of TypeVariables. This can happen when we are processing + // type parameters in a Method, while we already processed the type parameters of a class. + if (contextualActualTypeParameters.containsKey(typeParameter)) { + continue; + } } if (actualTypeArgument instanceof WildcardType) { @@ -147,7 +174,7 @@ private void registerTypeVariableIfNotPresent(TypeVariable typeVariable) { /** * @param typeParameter The TypeVariable parameter * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable - * then retrieve BoundedType of this TypeVariable + * then retrieve BoundedType of this TypeVariable */ private BoundedType boundsOf(TypeVariable typeParameter) { if (typeParameter.getBounds()[0] instanceof TypeVariable) { @@ -159,13 +186,17 @@ private BoundedType boundsOf(TypeVariable typeParameter) { /** * @param wildCard The WildCard type * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable - * then retrieve BoundedType of this TypeVariable + * then retrieve BoundedType of this TypeVariable */ private BoundedType boundsOf(WildcardType wildCard) { /* * According to JLS(http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1): - * - Lower and upper can't coexist: (for instance, this is not allowed: & super MyInterface>) - * - Multiple bounds are not supported (for instance, this is not allowed: & MyInterface>) + * - Lower and upper can't coexist: (for instance, this is not allowed: + * & super MyInterface>) + * - Multiple concrete type bounds are not supported (for instance, this is not allowed: + * & MyInterface>) + * But the following form is possible where there is a single concrete tyep bound followed by interface type bounds + * & Comparable> */ WildCardBoundedType wildCardBoundedType = new WildCardBoundedType(wildCard); @@ -241,7 +272,7 @@ public GenericMetadataSupport resolveGenericReturnType(Method method) { // logger.log("Method '" + method.toGenericString() + "' has return type : " + genericReturnType.getClass().getInterfaces()[0].getSimpleName() + " : " + genericReturnType); int arity = 0; - while(genericReturnType instanceof GenericArrayType) { + while (genericReturnType instanceof GenericArrayType) { arity++; genericReturnType = ((GenericArrayType) genericReturnType).getGenericComponentType(); } @@ -273,8 +304,8 @@ private GenericMetadataSupport resolveGenericType(Type type, Method method) { * Create an new instance of {@link GenericMetadataSupport} inferred from a {@link Type}. * *

- * At the moment type can only be a {@link Class} or a {@link ParameterizedType}, otherwise - * it'll throw a {@link MockitoException}. + * At the moment type can only be a {@link Class} or a {@link ParameterizedType}, otherwise + * it'll throw a {@link MockitoException}. *

* * @param type The class from which the {@link GenericMetadataSupport} should be built. @@ -300,7 +331,7 @@ public static GenericMetadataSupport inferFrom(Type type) { /** * Generic metadata implementation for {@link Class}. - * + *

* Offer support to retrieve generic metadata on a {@link Class} by reading type parameters and type variables on * the class and its ancestors and interfaces. */ @@ -322,10 +353,10 @@ public Class rawType() { /** * Generic metadata implementation for "standalone" {@link ParameterizedType}. - * + *

* Offer support to retrieve generic metadata on a {@link ParameterizedType} by reading type variables of * the related raw type and declared type variable of this parameterized type. - * + *

* This class is not designed to work on ParameterizedType returned by {@link Method#getGenericReturnType()}, as * the ParameterizedType instance return in these cases could have Type Variables that refer to type declaration(s). * That's what meant the "standalone" word at the beginning of the Javadoc. @@ -387,6 +418,7 @@ private static class TypeVariableReturnType extends GenericMetadataSupport { private final TypeVariable typeVariable; private final TypeVariable[] typeParameters; private Class rawType; + private List extraInterfaces; public TypeVariableReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, TypeVariable typeVariable) { this.typeParameters = typeParameters; @@ -405,7 +437,7 @@ private void readTypeVariables() { for (Type type : typeVariable.getBounds()) { registerTypeVariablesOn(type); } - registerTypeParametersOn(new TypeVariable[] { typeVariable }); + registerTypeParametersOn(new TypeVariable[]{typeVariable}); registerTypeVariablesOn(getActualTypeArgumentFor(typeVariable)); } @@ -419,15 +451,18 @@ public Class rawType() { @Override public List extraInterfaces() { + if (extraInterfaces != null) { + return extraInterfaces; + } Type type = extractActualBoundedTypeOf(typeVariable); if (type instanceof BoundedType) { - return Arrays.asList(((BoundedType) type).interfaceBounds()); + return extraInterfaces = Arrays.asList(((BoundedType) type).interfaceBounds()); } if (type instanceof ParameterizedType) { - return Collections.singletonList(type); + return extraInterfaces = Collections.singletonList(type); } if (type instanceof Class) { - return Collections.emptyList(); + return extraInterfaces = Collections.emptyList(); } throw new MockitoException("Cannot extract extra-interfaces from '" + typeVariable + "' : '" + type + "'"); } @@ -442,7 +477,7 @@ public Class[] rawExtraInterfaces() { for (Type extraInterface : extraInterfaces) { Class rawInterface = extractRawTypeOf(extraInterface); // avoid interface collision with actual raw type (with typevariables, resolution ca be quite aggressive) - if(!rawType().equals(rawInterface)) { + if (!rawType().equals(rawInterface)) { rawExtraInterfaces.add(rawInterface); } } @@ -514,7 +549,6 @@ public Class rawType() { } - /** * Type representing bounds of a type * @@ -537,7 +571,7 @@ public interface BoundedType extends Type { * *

If upper bounds are declared with SomeClass and additional interfaces, then firstBound will be SomeClass and * interfacesBound will be an array of the additional interfaces. - * + *

* i.e. SomeClass. *


      *     interface UpperBoundedTypeWithClass & Cloneable> {
diff --git a/src/main/java/org/mockito/invocation/Location.java b/src/main/java/org/mockito/invocation/Location.java
index 9337668283..43b48324d8 100644
--- a/src/main/java/org/mockito/invocation/Location.java
+++ b/src/main/java/org/mockito/invocation/Location.java
@@ -23,7 +23,7 @@ public interface Location {
      * Source file of this location
      *
      * @return source file
-     * @since 2.23.5
+     * @since 2.24.6
      */
     String getSourceFile();
 }
diff --git a/src/main/java/org/mockito/internal/listeners/StubbingLookupEvent.java b/src/main/java/org/mockito/listeners/StubbingLookupEvent.java
similarity index 87%
rename from src/main/java/org/mockito/internal/listeners/StubbingLookupEvent.java
rename to src/main/java/org/mockito/listeners/StubbingLookupEvent.java
index 0d45edd345..a03fb594ea 100644
--- a/src/main/java/org/mockito/internal/listeners/StubbingLookupEvent.java
+++ b/src/main/java/org/mockito/listeners/StubbingLookupEvent.java
@@ -2,7 +2,7 @@
  * Copyright (c) 2018 Mockito contributors
  * This program is made available under the terms of the MIT License.
  */
-package org.mockito.internal.listeners;
+package org.mockito.listeners;
 
 import org.mockito.invocation.Invocation;
 import org.mockito.mock.MockCreationSettings;
@@ -12,25 +12,32 @@
 
 /**
  * Represent an information about the looked up stubbing
+ *
+ * @since 2.24.6
  */
 public interface StubbingLookupEvent {
+
     /**
      * @return The invocation that causes stubbing lookup
+     * @since 2.24.6
      */
     Invocation getInvocation();
 
     /**
      * @return Looked up stubbing. It can be null, which indicates that the invocation was not stubbed
+     * @since 2.24.6
      */
     Stubbing getStubbingFound();
 
     /**
      * @return All stubbings declared on the mock object that we are invoking
+     * @since 2.24.6
      */
     Collection getAllStubbings();
 
     /**
      * @return Settings of the mock object that we are invoking
+     * @since 2.24.6
      */
     MockCreationSettings getMockSettings();
 }
diff --git a/src/main/java/org/mockito/listeners/StubbingLookupListener.java b/src/main/java/org/mockito/listeners/StubbingLookupListener.java
new file mode 100644
index 0000000000..b33e063462
--- /dev/null
+++ b/src/main/java/org/mockito/listeners/StubbingLookupListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.listeners;
+
+import org.mockito.MockSettings;
+import org.mockito.mock.MockCreationSettings;
+
+/**
+ * When a method is called on a mock object Mockito looks up any stubbings recorded on that mock.
+ * This listener gets notified on stubbing lookup.
+ * Register listener via {@link MockSettings#stubbingLookupListeners(StubbingLookupListener...)}.
+ * This API is used by Mockito to implement {@link org.mockito.exceptions.misusing.PotentialStubbingProblem}
+ * (part of Mockito {@link org.mockito.quality.Strictness}).
+ * 

+ * Details: When method is called on the mock object, Mockito looks for any answer (stubbing) declared on that mock. + * If the stubbed answer is found, that answer is then invoked (value returned, thrown exception, etc.). + * If the answer is not found (e.g. that invocation was not stubbed on the mock), mock's default answer is used. + * This listener implementation is notified when Mockito attempts to find an answer for invocation on a mock. + *

+ * The listeners can be accessed via {@link MockCreationSettings#getStubbingLookupListeners()}. + * + * @since 2.24.6 + */ +public interface StubbingLookupListener { + + /** + * Called by the framework when Mockito looked up an answer for invocation on a mock. + * For details, see {@link StubbingLookupListener}. + * + * @param stubbingLookupEvent - Information about the looked up stubbing + * @since 2.24.6 + */ + void onStubbingLookup(StubbingLookupEvent stubbingLookupEvent); +} diff --git a/src/main/java/org/mockito/mock/MockCreationSettings.java b/src/main/java/org/mockito/mock/MockCreationSettings.java index 7e74be8a90..2343b0da64 100644 --- a/src/main/java/org/mockito/mock/MockCreationSettings.java +++ b/src/main/java/org/mockito/mock/MockCreationSettings.java @@ -9,6 +9,7 @@ import org.mockito.MockSettings; import org.mockito.NotExtensible; import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.listeners.VerificationStartedListener; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -70,7 +71,17 @@ public interface MockCreationSettings { boolean isStripAnnotations(); /** - * {@link InvocationListener} instances attached to this mock, see {@link org.mockito.MockSettings#invocationListeners}. + * Returns {@link StubbingLookupListener} instances attached to this mock via {@link MockSettings#stubbingLookupListeners(StubbingLookupListener...)}. + * The resulting list is mutable, you can add/remove listeners even after the mock was created. + *

+ * For more details see {@link StubbingLookupListener}. + * + * @since 2.24.6 + */ + List getStubbingLookupListeners(); + + /** + * {@link InvocationListener} instances attached to this mock, see {@link org.mockito.MockSettings#invocationListeners(InvocationListener...)}. */ List getInvocationListeners(); diff --git a/src/test/java/org/mockito/internal/creation/MockSettingsImplTest.java b/src/test/java/org/mockito/internal/creation/MockSettingsImplTest.java index 18911e4ddf..63cb1e7218 100644 --- a/src/test/java/org/mockito/internal/creation/MockSettingsImplTest.java +++ b/src/test/java/org/mockito/internal/creation/MockSettingsImplTest.java @@ -5,25 +5,31 @@ package org.mockito.internal.creation; import org.assertj.core.api.Assertions; +import org.assertj.core.api.ThrowableAssert; import org.junit.Test; import org.mockito.Mock; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.debugging.VerboseMockInvocationLogger; import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.StubbingLookupListener; import org.mockitoutil.TestBase; import java.util.LinkedList; import java.util.List; import java.util.Set; -import static org.junit.Assert.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class MockSettingsImplTest extends TestBase { private MockSettingsImpl mockSettingsImpl = new MockSettingsImpl(); @Mock private InvocationListener invocationListener; + @Mock private StubbingLookupListener stubbingLookupListener; @Test(expected=MockitoException.class) @SuppressWarnings("unchecked") @@ -112,12 +118,6 @@ public void shouldAddVerboseLoggingListenerOnlyOnce() { Assertions.assertThat(mockSettingsImpl.getInvocationListeners()).hasSize(1); } - @SuppressWarnings("unchecked") - @Test(expected=MockitoException.class) - public void shouldNotAllowNullListener() { - mockSettingsImpl.invocationListeners((InvocationListener[])null); - } - @Test @SuppressWarnings("unchecked") public void shouldAddInvocationListener() { @@ -145,22 +145,98 @@ public void canAddDuplicateInvocationListeners_ItsNotOurBusinessThere() { } @Test - public void shouldReportErrorWhenAddingNoInvocationListeners() throws Exception { - try { - mockSettingsImpl.invocationListeners(); - fail(); - } catch (Exception e) { - Assertions.assertThat(e.getMessage()).contains("at least one listener"); - } + public void validates_listeners() { + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.addListeners(new Object[] {}, new LinkedList(), "myListeners"); + } + }).hasMessageContaining("myListeners() requires at least one listener"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.addListeners(null, new LinkedList(), "myListeners"); + } + }).hasMessageContaining("myListeners() does not accept null vararg array"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.addListeners(new Object[] {null}, new LinkedList(), "myListeners"); + } + }).hasMessageContaining("myListeners() does not accept null listeners"); + } + + + @Test + public void validates_stubbing_lookup_listeners() { + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.stubbingLookupListeners(new StubbingLookupListener[] {}); + } + }).hasMessageContaining("stubbingLookupListeners() requires at least one listener"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.stubbingLookupListeners(null); + } + }).hasMessageContaining("stubbingLookupListeners() does not accept null vararg array"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.stubbingLookupListeners(new StubbingLookupListener[] {null}); + } + }).hasMessageContaining("stubbingLookupListeners() does not accept null listeners"); + } + + @Test + public void validates_invocation_listeners() { + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.invocationListeners(new InvocationListener[] {}); + } + }).hasMessageContaining("invocationListeners() requires at least one listener"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.invocationListeners(null); + } + }).hasMessageContaining("invocationListeners() does not accept null vararg array"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + public void call() { + mockSettingsImpl.invocationListeners(new InvocationListener[] {null}); + } + }).hasMessageContaining("invocationListeners() does not accept null listeners"); + } + + @Test + public void addListeners_has_empty_listeners_by_default() { + assertTrue(mockSettingsImpl.getInvocationListeners().isEmpty()); + assertTrue(mockSettingsImpl.getStubbingLookupListeners().isEmpty()); + } + + @Test + public void addListeners_shouldAddMockObjectListeners() { + //when + mockSettingsImpl.invocationListeners(invocationListener); + mockSettingsImpl.stubbingLookupListeners(stubbingLookupListener); + + //then + assertThat(mockSettingsImpl.getInvocationListeners()).contains(invocationListener); + assertThat(mockSettingsImpl.getStubbingLookupListeners()).contains(stubbingLookupListener); } @Test - public void shouldReportErrorWhenAddingANullInvocationListener() throws Exception { - try { - mockSettingsImpl.invocationListeners(invocationListener, null); - fail(); - } catch (Exception e) { - Assertions.assertThat(e.getMessage()).contains("does not accept null"); - } + public void addListeners_canAddDuplicateMockObjectListeners_ItsNotOurBusinessThere() { + //when + mockSettingsImpl.stubbingLookupListeners(stubbingLookupListener) + .stubbingLookupListeners(stubbingLookupListener) + .invocationListeners(invocationListener) + .invocationListeners(invocationListener); + + //then + assertThat(mockSettingsImpl.getInvocationListeners()) + .containsSequence(invocationListener, invocationListener); + assertThat(mockSettingsImpl.getStubbingLookupListeners()) + .containsSequence(stubbingLookupListener, stubbingLookupListener); } } diff --git a/src/test/java/org/mockito/internal/listeners/StubbingLookupNotifierTest.java b/src/test/java/org/mockito/internal/listeners/StubbingLookupNotifierTest.java index ac963c4855..40e5396920 100644 --- a/src/test/java/org/mockito/internal/listeners/StubbingLookupNotifierTest.java +++ b/src/test/java/org/mockito/internal/listeners/StubbingLookupNotifierTest.java @@ -9,6 +9,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.invocation.Invocation; +import org.mockito.listeners.StubbingLookupListener; import org.mockito.stubbing.Stubbing; import org.mockitoutil.TestBase; diff --git a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsGenericDeepStubsTest.java b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsGenericDeepStubsTest.java index d91df8c40e..b051e124a8 100644 --- a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsGenericDeepStubsTest.java +++ b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsGenericDeepStubsTest.java @@ -5,39 +5,52 @@ package org.mockito.internal.stubbing.defaultanswers; import org.junit.Test; +import org.mockitousage.examples.use.Article; +import java.io.Closeable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @SuppressWarnings("unused") public class ReturnsGenericDeepStubsTest { - interface ListOfInteger extends List {} + interface ListOfInteger extends List { + } - interface AnotherListOfInteger extends ListOfInteger {} + interface AnotherListOfInteger extends ListOfInteger { + } interface GenericsNest & Cloneable> extends Map> { Set remove(Object key); // override with fixed ParameterizedType + List returningWildcard(); + Map returningNonMockableNestedGeneric(); + K returningK(); + List paramTypeWithTypeParams(); + T twoTypeParams(S s); + O typeVarWithTypeParams(); + Number returnsNormalType(); } @Test - public void generic_deep_mock_frenzy__look_at_these_chained_calls() throws Exception { + public void generic_deep_mock_frenzy__look_at_these_chained_calls() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); Set>> entries = mock.entrySet(); - Iterator>> entriesIterator = mock.entrySet().iterator(); + Iterator>> entriesIterator = mock.entrySet().iterator(); Map.Entry> nextEntry = mock.entrySet().iterator().next(); Cloneable cloneableKey = mock.entrySet().iterator().next().getKey(); @@ -49,17 +62,17 @@ public void generic_deep_mock_frenzy__look_at_these_chained_calls() throws Excep } @Test - public void can_create_mock_from_multiple_type_variable_bounds_when_return_type_of_parameterized_method_is_a_parameterizedtype_that_is_referencing_a_typevar_on_class() throws Exception { + public void can_create_mock_from_multiple_type_variable_bounds_when_return_type_of_parameterized_method_is_a_parameterizedType_that_is_referencing_a_typeVar_on_class() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); Cloneable cloneable_bound_that_is_declared_on_typevar_K_in_the_class_which_is_referenced_by_typevar_O_declared_on_the_method = - mock.paramTypeWithTypeParams().get(0); + mock.paramTypeWithTypeParams().get(0); Comparable comparable_bound_that_is_declared_on_typevar_K_in_the_class_which_is_referenced_by_typevar_O_declared_on_the_method = - mock.paramTypeWithTypeParams().get(0); + mock.paramTypeWithTypeParams().get(0); } @Test - public void can_create_mock_from_multiple_type_variable_bounds_when_method_return_type_is_referencing_a_typevar_on_class() throws Exception { + public void can_create_mock_from_multiple_type_variable_bounds_when_method_return_type_is_referencing_a_typeVar_on_class() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); Cloneable cloneable_bound_of_typevar_K = mock.returningK(); @@ -67,7 +80,7 @@ public void can_create_mock_from_multiple_type_variable_bounds_when_method_retur } @Test - public void can_create_mock_from_multiple_type_variable_bounds_when_return_type_of_parameterized_method_is_a_typevar_that_is_referencing_a_typevar_on_class() throws Exception { + public void can_create_mock_from_multiple_type_variable_bounds_when_return_type_of_parameterized_method_is_a_typeVar_that_is_referencing_a_typeVar_on_class() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); Cloneable cloneable_bound_of_typevar_K_referenced_by_typevar_O = (Cloneable) mock.typeVarWithTypeParams(); @@ -75,7 +88,7 @@ public void can_create_mock_from_multiple_type_variable_bounds_when_return_type_ } @Test - public void can_create_mock_from_return_types_declared_with_a_bounded_wildcard() throws Exception { + public void can_create_mock_from_return_types_declared_with_a_bounded_wildcard() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); List objects = mock.returningWildcard(); @@ -84,7 +97,7 @@ public void can_create_mock_from_return_types_declared_with_a_bounded_wildcard() } @Test - public void can_still_work_with_raw_type_in_the_return_type() throws Exception { + public void can_still_work_with_raw_type_in_the_return_type() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); Number the_raw_type_that_should_be_returned = mock.returnsNormalType(); @@ -92,7 +105,7 @@ public void can_still_work_with_raw_type_in_the_return_type() throws Exception { } @Test - public void will_return_default_value_on_non_mockable_nested_generic() throws Exception { + public void will_return_default_value_on_non_mockable_nested_generic() { GenericsNest genericsNest = mock(GenericsNest.class, RETURNS_DEEP_STUBS); ListOfInteger listOfInteger = mock(ListOfInteger.class, RETURNS_DEEP_STUBS); AnotherListOfInteger anotherListOfInteger = mock(AnotherListOfInteger.class, RETURNS_DEEP_STUBS); @@ -103,11 +116,80 @@ public void will_return_default_value_on_non_mockable_nested_generic() throws Ex } @Test(expected = ClassCastException.class) - public void as_expected_fail_with_a_CCE_on_callsite_when_erasure_takes_place_for_example___StringBuilder_is_subject_to_erasure() throws Exception { + public void as_expected_fail_with_a_CCE_on_call_site_when_erasure_takes_place_for_example___StringBuilder_is_subject_to_erasure() { GenericsNest mock = mock(GenericsNest.class, RETURNS_DEEP_STUBS); // following assignment needed to create a ClassCastException on the call site (i.e. : here) StringBuilder stringBuilder_assignment_that_should_throw_a_CCE = - mock.twoTypeParams(new StringBuilder()).append(2).append(3); + mock.twoTypeParams(new StringBuilder()).append(2).append(3); + } + + class WithGenerics { + T execute() { + throw new IllegalArgumentException(); + } + } + + class SubClass extends WithGenerics { + } + + class UserOfSubClass { + SubClass generate() { + return null; + } + } + + @Test + public void can_handle_deep_stubs_with_generics_at_end_of_deep_invocation() { + UserOfSubClass mock = mock(UserOfSubClass.class, RETURNS_DEEP_STUBS); + + when(mock.generate().execute()).thenReturn("sub"); + + assertThat(mock.generate().execute()).isEqualTo("sub"); + } + + public interface TopInterface { + T generic(); + } + + public interface MiddleInterface extends TopInterface { + } + + public class OwningClassWithDeclaredUpperBounds & Callable
& Closeable> { + public abstract class AbstractInner implements MiddleInterface { + } + } + + @Test + public void cannot_handle_deep_stubs_with_generics_declared_upper_bounds_at_end_of_deep_invocation() throws Exception { + OwningClassWithDeclaredUpperBounds.AbstractInner mock = + mock(OwningClassWithDeclaredUpperBounds.AbstractInner.class, RETURNS_DEEP_STUBS); + + // It seems that while the syntax used on OwningClassWithDeclaredUpperBounds.AbstractInner + // appear to be legal, the javac compiler does not follow through + // hence we need casting, this may also explain why GenericMetadataSupport has trouble to + // extract matching data as well. + + assertThat(mock.generic()) + .describedAs("mock should implement first bound : 'Iterable'") + .isInstanceOf(Iterable.class); + assertThat(((Iterable
) mock.generic()).iterator()) + .describedAs("Iterable returns Iterator").isInstanceOf(Iterator.class); + assertThat(((Iterable
) mock.generic()).iterator().next()) + .describedAs("Cannot yet extract Type argument 'Article' so return null instead of a mock " + + "of type Object (which would raise CCE on the call-site)") + .isNull(); + + assertThat(mock.generic()) + .describedAs("mock should implement second interface bound : 'Callable'") + .isInstanceOf(Callable.class); + assertThat(((Callable
) mock.generic()).call()) + .describedAs("Cannot yet extract Type argument 'Article' so return null instead of a mock " + + "of type Object (which would raise CCE on the call-site)") + .isNull(); + + assertThat(mock.generic()) + .describedAs("mock should implement third interface bound : 'Closeable'") + .isInstanceOf(Closeable.class); } } diff --git a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocksTest.java b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocksTest.java index 7de160c09e..ba5b822d30 100755 --- a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocksTest.java +++ b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMocksTest.java @@ -6,15 +6,24 @@ import org.junit.Test; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.internal.stubbing.defaultanswers.ReturnsGenericDeepStubsTest.WithGenerics; import org.mockito.internal.util.MockUtil; import org.mockitoutil.TestBase; import static org.junit.Assert.*; import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.when; public class ReturnsMocksTest extends TestBase { private ReturnsMocks values = new ReturnsMocks(); + interface AllInterface { + FooInterface getInterface(); + BarClass getNormalClass(); + Baz getFinalClass(); + WithGenerics withGenerics(); + } + interface FooInterface { } @@ -25,21 +34,30 @@ final class Baz { } @Test - public void should_return_mock_value_for_interface() throws Exception { - Object interfaceMock = values.returnValueFor(FooInterface.class); + public void should_return_mock_value_for_interface() throws Throwable { + Object interfaceMock = values.answer(invocationOf(AllInterface.class, "getInterface")); assertTrue(MockUtil.isMock(interfaceMock)); } @Test - public void should_return_mock_value_for_class() throws Exception { - Object classMock = values.returnValueFor(BarClass.class); + public void should_return_mock_value_for_class() throws Throwable { + Object classMock = values.answer(invocationOf(AllInterface.class, "getNormalClass")); + assertTrue(MockUtil.isMock(classMock)); + } + + @SuppressWarnings("unchecked") + @Test + public void should_return_mock_value_for_generic_class() throws Throwable { + WithGenerics classMock = (WithGenerics) values.answer(invocationOf(AllInterface.class, "withGenerics")); assertTrue(MockUtil.isMock(classMock)); + when(classMock.execute()).thenReturn("return"); + assertEquals("return", classMock.execute()); } @Test - public void should_return_null_for_final_class_if_unsupported() throws Exception { + public void should_return_null_for_final_class_if_unsupported() throws Throwable { assumeFalse(Plugins.getMockMaker().isTypeMockable(Baz.class).mockable()); - assertNull(values.returnValueFor(Baz.class)); + assertNull(values.answer(invocationOf(AllInterface.class, "getFinalClass"))); } @Test diff --git a/src/test/java/org/mockito/internal/util/reflection/GenericMetadataSupportTest.java b/src/test/java/org/mockito/internal/util/reflection/GenericMetadataSupportTest.java index 323efbf843..cc5b5885d1 100644 --- a/src/test/java/org/mockito/internal/util/reflection/GenericMetadataSupportTest.java +++ b/src/test/java/org/mockito/internal/util/reflection/GenericMetadataSupportTest.java @@ -8,9 +8,16 @@ import java.io.Serializable; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -21,32 +28,67 @@ public class GenericMetadataSupportTest { interface GenericsSelfReference> { T self(); } + interface UpperBoundedTypeWithClass> { E get(); } + interface UpperBoundedTypeWithInterfaces & Cloneable> { E get(); } - interface ListOfNumbers extends List {} - interface AnotherListOfNumbers extends ListOfNumbers {} - abstract class ListOfNumbersImpl implements ListOfNumbers {} - abstract class AnotherListOfNumbersImpl extends ListOfNumbersImpl {} + interface ListOfNumbers extends List { + } + + interface AnotherListOfNumbers extends ListOfNumbers { + } + + abstract class ListOfNumbersImpl implements ListOfNumbers { + } + + abstract class AnotherListOfNumbersImpl extends ListOfNumbersImpl { + } - interface ListOfAnyNumbers extends List {} + interface ListOfAnyNumbers extends List { + } interface GenericsNest & Cloneable> extends Map> { Set remove(Object key); // override with fixed ParameterizedType + List returning_wildcard_with_class_lower_bound(); + List returning_wildcard_with_typeVar_lower_bound(); + List returning_wildcard_with_typeVar_upper_bound(); + K returningK(); + List paramType_with_type_params(); + T two_type_params(); + O typeVar_with_type_params(); } - static class StringList extends ArrayList { } + static class StringList extends ArrayList { + } + + public interface TopInterface { + T generic(); + } + + public interface MiddleInterface extends TopInterface { + } + + public class OwningClassWithDeclaredUpperBounds & Comparable & Cloneable> { + public abstract class AbstractInner implements MiddleInterface { + } + } + + public class OwningClassWithNoDeclaredUpperBounds { + public abstract class AbstractInner implements MiddleInterface { + } + } @Test public void typeVariable_of_self_type() { @@ -56,7 +98,7 @@ public void typeVariable_of_self_type() { } @Test - public void can_get_raw_type_from_Class() throws Exception { + public void can_get_raw_type_from_Class() { assertThat(inferFrom(ListOfAnyNumbers.class).rawType()).isEqualTo(ListOfAnyNumbers.class); assertThat(inferFrom(ListOfNumbers.class).rawType()).isEqualTo(ListOfNumbers.class); assertThat(inferFrom(GenericsNest.class).rawType()).isEqualTo(GenericsNest.class); @@ -64,7 +106,7 @@ public void can_get_raw_type_from_Class() throws Exception { } @Test - public void can_get_raw_type_from_ParameterizedType() throws Exception { + public void can_get_raw_type_from_ParameterizedType() { assertThat(inferFrom(ListOfAnyNumbers.class.getGenericInterfaces()[0]).rawType()).isEqualTo(List.class); assertThat(inferFrom(ListOfNumbers.class.getGenericInterfaces()[0]).rawType()).isEqualTo(List.class); assertThat(inferFrom(GenericsNest.class.getGenericInterfaces()[0]).rawType()).isEqualTo(Map.class); @@ -72,7 +114,7 @@ public void can_get_raw_type_from_ParameterizedType() throws Exception { } @Test - public void can_get_type_variables_from_Class() throws Exception { + public void can_get_type_variables_from_Class() { assertThat(inferFrom(GenericsNest.class).actualTypeArguments().keySet()).hasSize(1).extracting("name").contains("K"); assertThat(inferFrom(ListOfNumbers.class).actualTypeArguments().keySet()).isEmpty(); assertThat(inferFrom(ListOfAnyNumbers.class).actualTypeArguments().keySet()).hasSize(1).extracting("name").contains("N"); @@ -89,7 +131,7 @@ public void can_resolve_type_variables_from_ancestors() throws Exception { } @Test - public void can_get_type_variables_from_ParameterizedType() throws Exception { + public void can_get_type_variables_from_ParameterizedType() { assertThat(inferFrom(GenericsNest.class.getGenericInterfaces()[0]).actualTypeArguments().keySet()).hasSize(2).extracting("name").contains("K", "V"); assertThat(inferFrom(ListOfAnyNumbers.class.getGenericInterfaces()[0]).actualTypeArguments().keySet()).hasSize(1).extracting("name").contains("E"); assertThat(inferFrom(Integer.class.getGenericInterfaces()[0]).actualTypeArguments().keySet()).hasSize(1).extracting("name").contains("T"); @@ -98,7 +140,7 @@ public void can_get_type_variables_from_ParameterizedType() throws Exception { } @Test - public void typeVariable_return_type_of____iterator____resolved_to_Iterator_and_type_argument_to_String() throws Exception { + public void typeVariable_return_type_of____iterator____resolved_to_Iterator_and_type_argument_to_String() { GenericMetadataSupport genericMetadata = inferFrom(StringList.class).resolveGenericReturnType(firstNamedMethod("iterator", StringList.class)); assertThat(genericMetadata.rawType()).isEqualTo(Iterator.class); @@ -106,7 +148,7 @@ public void typeVariable_return_type_of____iterator____resolved_to_Iterator_and_ } @Test - public void typeVariable_return_type_of____get____resolved_to_Set_and_type_argument_to_Number() throws Exception { + public void typeVariable_return_type_of____get____resolved_to_Set_and_type_argument_to_Number() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("get", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(Set.class); @@ -114,7 +156,7 @@ public void typeVariable_return_type_of____get____resolved_to_Set_and_type_argum } @Test - public void bounded_typeVariable_return_type_of____returningK____resolved_to_Comparable_and_with_BoundedType() throws Exception { + public void bounded_typeVariable_return_type_of____returningK____resolved_to_Comparable_and_with_BoundedType() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("returningK", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(Comparable.class); @@ -123,7 +165,7 @@ public void bounded_typeVariable_return_type_of____returningK____resolved_to_Com } @Test - public void fixed_ParamType_return_type_of____remove____resolved_to_Set_and_type_argument_to_Number() throws Exception { + public void fixed_ParamType_return_type_of____remove____resolved_to_Set_and_type_argument_to_Number() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("remove", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(Set.class); @@ -131,7 +173,7 @@ public void fixed_ParamType_return_type_of____remove____resolved_to_Set_and_type } @Test - public void paramType_return_type_of____values____resolved_to_Collection_and_type_argument_to_Parameterized_Set() throws Exception { + public void paramType_return_type_of____values____resolved_to_Collection_and_type_argument_to_Parameterized_Set() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("values", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(Collection.class); @@ -141,7 +183,7 @@ public void paramType_return_type_of____values____resolved_to_Collection_and_typ } @Test - public void paramType_with_type_parameters_return_type_of____paramType_with_type_params____resolved_to_Collection_and_type_argument_to_Parameterized_Set() throws Exception { + public void paramType_with_type_parameters_return_type_of____paramType_with_type_params____resolved_to_Collection_and_type_argument_to_Parameterized_Set() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("paramType_with_type_params", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(List.class); @@ -150,7 +192,7 @@ public void paramType_with_type_parameters_return_type_of____paramType_with_type } @Test - public void typeVariable_with_type_parameters_return_type_of____typeVar_with_type_params____resolved_K_hence_to_Comparable_and_with_BoundedType() throws Exception { + public void typeVariable_with_type_parameters_return_type_of____typeVar_with_type_params____resolved_K_hence_to_Comparable_and_with_BoundedType() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("typeVar_with_type_params", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(Comparable.class); @@ -159,7 +201,7 @@ public void typeVariable_with_type_parameters_return_type_of____typeVar_with_typ } @Test - public void class_return_type_of____append____resolved_to_StringBuilder_and_type_arguments() throws Exception { + public void class_return_type_of____append____resolved_to_StringBuilder_and_type_arguments() { GenericMetadataSupport genericMetadata = inferFrom(StringBuilder.class).resolveGenericReturnType(firstNamedMethod("append", StringBuilder.class)); assertThat(genericMetadata.rawType()).isEqualTo(StringBuilder.class); @@ -167,9 +209,8 @@ public void class_return_type_of____append____resolved_to_StringBuilder_and_type } - @Test - public void paramType_with_wildcard_return_type_of____returning_wildcard_with_class_lower_bound____resolved_to_List_and_type_argument_to_Integer() throws Exception { + public void paramType_with_wildcard_return_type_of____returning_wildcard_with_class_lower_bound____resolved_to_List_and_type_argument_to_Integer() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("returning_wildcard_with_class_lower_bound", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(List.class); @@ -179,17 +220,18 @@ public void paramType_with_wildcard_return_type_of____returning_wildcard_with_cl } @Test - public void paramType_with_wildcard_return_type_of____returning_wildcard_with_typeVar_lower_bound____resolved_to_List_and_type_argument_to_Integer() throws Exception { + public void paramType_with_wildcard_return_type_of____returning_wildcard_with_typeVar_lower_bound____resolved_to_List_and_type_argument_to_Integer() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("returning_wildcard_with_typeVar_lower_bound", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(List.class); GenericMetadataSupport.BoundedType boundedType = (GenericMetadataSupport.BoundedType) typeVariableValue(genericMetadata.actualTypeArguments(), "E"); assertThat(inferFrom(boundedType.firstBound()).rawType()).isEqualTo(Comparable.class); - assertThat(boundedType.interfaceBounds()).contains(Cloneable.class); } + assertThat(boundedType.interfaceBounds()).contains(Cloneable.class); + } @Test - public void paramType_with_wildcard_return_type_of____returning_wildcard_with_typeVar_upper_bound____resolved_to_List_and_type_argument_to_Integer() throws Exception { + public void paramType_with_wildcard_return_type_of____returning_wildcard_with_typeVar_upper_bound____resolved_to_List_and_type_argument_to_Integer() { GenericMetadataSupport genericMetadata = inferFrom(GenericsNest.class).resolveGenericReturnType(firstNamedMethod("returning_wildcard_with_typeVar_upper_bound", GenericsNest.class)); assertThat(genericMetadata.rawType()).isEqualTo(List.class); @@ -199,6 +241,75 @@ public void paramType_with_wildcard_return_type_of____returning_wildcard_with_ty assertThat(boundedType.interfaceBounds()).contains(Cloneable.class); } + @Test + public void can_extract_raw_type_from_bounds_on_terminal_typeVariable() { + assertThat(inferFrom(OwningClassWithDeclaredUpperBounds.AbstractInner.class) + .resolveGenericReturnType(firstNamedMethod("generic", OwningClassWithDeclaredUpperBounds.AbstractInner.class)) + .rawType() + ).isEqualTo(List.class); + assertThat(inferFrom(OwningClassWithNoDeclaredUpperBounds.AbstractInner.class) + .resolveGenericReturnType(firstNamedMethod("generic", OwningClassWithNoDeclaredUpperBounds.AbstractInner.class)) + .rawType() + ).isEqualTo(Object.class); + } + + @Test + public void can_extract_interface_type_from_bounds_on_terminal_typeVariable() { + + assertThat(inferFrom(OwningClassWithDeclaredUpperBounds.AbstractInner.class) + .resolveGenericReturnType(firstNamedMethod("generic", OwningClassWithDeclaredUpperBounds.AbstractInner.class)) + .rawExtraInterfaces() + ).containsExactly(Comparable.class, Cloneable.class); + assertThat(inferFrom(OwningClassWithDeclaredUpperBounds.AbstractInner.class) + .resolveGenericReturnType(firstNamedMethod("generic", OwningClassWithDeclaredUpperBounds.AbstractInner.class)) + .extraInterfaces() + ).containsExactly(parameterizedTypeOf(Comparable.class, null, String.class), + Cloneable.class); + + assertThat(inferFrom(OwningClassWithNoDeclaredUpperBounds.AbstractInner.class) + .resolveGenericReturnType(firstNamedMethod("generic", OwningClassWithNoDeclaredUpperBounds.AbstractInner.class)) + .extraInterfaces() + ).isEmpty(); + } + + private ParameterizedType parameterizedTypeOf(final Class rawType, final Class ownerType, final Type... actualTypeArguments) { + return new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + @Override + public Type getRawType() { + return rawType; + } + + @Override + public Type getOwnerType() { + return ownerType; + } + + public boolean equals(Object other) { + if (other instanceof ParameterizedType) { + ParameterizedType otherParamType = (ParameterizedType) other; + if (this == otherParamType) { + return true; + } else { + return equals(ownerType, otherParamType.getOwnerType()) + && equals(rawType, otherParamType.getRawType()) + && Arrays.equals(actualTypeArguments, otherParamType.getActualTypeArguments()); + } + } else { + return false; + } + } + + private boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + }; + } + private Type typeVariableValue(Map, Type> typeVariables, String typeVariableName) { for (Map.Entry, Type> typeVariableTypeEntry : typeVariables.entrySet()) { if (typeVariableTypeEntry.getKey().getName().equals(typeVariableName)) { diff --git a/src/test/java/org/mockitousage/debugging/StubbingLookupListenerCallbackTest.java b/src/test/java/org/mockitousage/debugging/StubbingLookupListenerCallbackTest.java new file mode 100644 index 0000000000..280fc61962 --- /dev/null +++ b/src/test/java/org/mockitousage/debugging/StubbingLookupListenerCallbackTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.debugging; + +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InOrder; +import org.mockito.listeners.StubbingLookupEvent; +import org.mockito.listeners.StubbingLookupListener; +import org.mockito.mock.MockCreationSettings; +import org.mockitousage.IMethods; +import org.mockitoutil.ConcurrentTesting; +import org.mockitoutil.TestBase; + +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +public class StubbingLookupListenerCallbackTest extends TestBase { + + StubbingLookupListener listener = mock(StubbingLookupListener.class); + StubbingLookupListener listener2 = mock(StubbingLookupListener.class); + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener)); + + @Test + public void should_call_listener_when_mock_return_normally_with_stubbed_answer() { + // given + doReturn("coke").when(mock).giveMeSomeString("soda"); + doReturn("java").when(mock).giveMeSomeString("coffee"); + + // when + mock.giveMeSomeString("soda"); + + // then + verify(listener).onStubbingLookup(argThat(new ArgumentMatcher() { + @Override + public boolean matches(StubbingLookupEvent argument) { + assertEquals("soda", argument.getInvocation().getArgument(0)); + assertEquals("mock", argument.getMockSettings().getMockName().toString()); + assertEquals(2, argument.getAllStubbings().size()); + assertNotNull(argument.getStubbingFound()); + return true; + } + })); + } + + @Test + public void should_call_listener_when_mock_return_normally_with_default_answer() { + // given + doReturn("java").when(mock).giveMeSomeString("coffee"); + + // when + mock.giveMeSomeString("soda"); + + // then + verify(listener).onStubbingLookup(argThat(new ArgumentMatcher() { + @Override + public boolean matches(StubbingLookupEvent argument) { + assertEquals("soda", argument.getInvocation().getArgument(0)); + assertEquals("mock", argument.getMockSettings().getMockName().toString()); + assertEquals(1, argument.getAllStubbings().size()); + assertNull(argument.getStubbingFound()); + return true; + } + })); + } + + @Test + public void should_not_call_listener_when_mock_is_not_called() { + // when stubbing is recorded + doReturn("java").when(mock).giveMeSomeString("coffee"); + + // then + verifyZeroInteractions(listener); + } + + @Test + public void should_allow_same_listener() { + // given + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener, listener)); + + // when + mock.giveMeSomeString("tea"); + mock.giveMeSomeString("coke"); + + // then each listener was notified 2 times (notified 4 times in total) + verify(listener, times(4)).onStubbingLookup(any(StubbingLookupEvent.class)); + } + + @Test + public void should_call_all_listeners_in_order() { + // given + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener, listener2)); + doReturn("sprite").when(mock).giveMeSomeString("soda"); + + // when + mock.giveMeSomeString("soda"); + + // then + InOrder inOrder = inOrder(listener, listener2); + inOrder.verify(listener).onStubbingLookup(any(StubbingLookupEvent.class)); + inOrder.verify(listener2).onStubbingLookup(any(StubbingLookupEvent.class)); + } + + @Test + public void should_call_all_listeners_when_mock_throws_exception() { + // given + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener, listener2)); + doThrow(new NoWater()).when(mock).giveMeSomeString("tea"); + + // when + try { + mock.giveMeSomeString("tea"); + fail(); + } catch (NoWater e) { + // then + verify(listener).onStubbingLookup(any(StubbingLookupEvent.class)); + verify(listener2).onStubbingLookup(any(StubbingLookupEvent.class)); + } + } + + @Test + public void should_delete_listener() { + // given + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener, listener2)); + + // when + mock.doSomething("1"); + mockingDetails(mock).getMockCreationSettings().getStubbingLookupListeners().remove(listener2); + mock.doSomething("2"); + + // then + verify(listener, times(2)).onStubbingLookup(any(StubbingLookupEvent.class)); + verify(listener2, times(1)).onStubbingLookup(any(StubbingLookupEvent.class)); + } + + @Test + public void should_clear_listeners() { + // given + Foo mock = mock(Foo.class, withSettings().stubbingLookupListeners(listener, listener2)); + + // when + mockingDetails(mock).getMockCreationSettings().getStubbingLookupListeners().clear(); + mock.doSomething("foo"); + + // then + verifyZeroInteractions(listener, listener2); + } + + @Test + public void add_listeners_concurrently_sanity_check() throws Exception { + //given + final IMethods mock = mock(IMethods.class); + final MockCreationSettings settings = mockingDetails(mock).getMockCreationSettings(); + + List runnables = new LinkedList(); + for (int i = 0; i < 50; i++) { + runnables.add(new Runnable() { + public void run() { + StubbingLookupListener listener1 = mock(StubbingLookupListener.class); + StubbingLookupListener listener2 = mock(StubbingLookupListener.class); + settings.getStubbingLookupListeners().add(listener1); + settings.getStubbingLookupListeners().add(listener2); + settings.getStubbingLookupListeners().remove(listener1); + } + }); + } + + //when + ConcurrentTesting.concurrently(runnables.toArray(new Runnable[runnables.size()])); + + //then + //This assertion may be flaky. If it is let's fix it or remove the test. For now, I'm keeping the test. + assertEquals(50, settings.getStubbingLookupListeners().size()); + } + + private static class NoWater extends RuntimeException {} +} diff --git a/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java b/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java index 8f4c420eca..9b4cc1ec1c 100644 --- a/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java +++ b/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java @@ -8,13 +8,14 @@ import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import org.mockito.ArgumentMatcher; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; import org.mockitousage.IMethods; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.mockito.Mockito.*; public class VerificationCollectorImplTest { @@ -46,14 +47,54 @@ public void should_collect_multiple_verification_failures() { IMethods methods = mock(IMethods.class); + methods.intArgumentMethod(6); + verify(methods).simpleMethod(); verify(methods).byteReturningMethod(); + verify(methods).intArgumentMethod(8); + verify(methods).longArg(8L); try { collector.collectAndReport(); - fail(); + failBecauseExceptionWasNotThrown(MockitoAssertionError.class); } catch (MockitoAssertionError error) { assertThat(error).hasMessageContaining("1. Wanted but not invoked:"); assertThat(error).hasMessageContaining("2. Wanted but not invoked:"); + assertThat(error).hasMessageContaining("3. Argument(s) are different! Wanted:"); + assertThat(error).hasMessageContaining("4. Wanted but not invoked:"); + } + } + + @Test + public void should_collect_matching_error_from_non_matching_arguments() { + VerificationCollector collector = MockitoJUnit.collector().assertLazily(); + + IMethods methods = mock(IMethods.class); + + methods.intArgumentMethod(6); + methods.longArg(8L); + methods.forShort((short)6); + + verify(methods).intArgumentMethod(8); + verify(methods).longArg(longThat(new ArgumentMatcher() { + @Override + public boolean matches(Long argument) { + throw new AssertionError("custom error message"); + } + })); + verify(methods).forShort(shortThat(new ArgumentMatcher() { + @Override + public boolean matches(Short argument) { + return false; + } + })); + + try { + collector.collectAndReport(); + failBecauseExceptionWasNotThrown(MockitoAssertionError.class); + } catch (MockitoAssertionError error) { + assertThat(error).hasMessageContaining("1. Argument(s) are different! Wanted:"); + assertThat(error).hasMessageContaining("2. custom error message"); + assertThat(error).hasMessageContaining("3. Argument(s) are different! Wanted:"); } } @@ -66,7 +107,7 @@ public void should_only_collect_failures_ignore_successful_verifications() { verify(methods, never()).simpleMethod(); verify(methods).byteReturningMethod(); - this.assertAtLeastOneFailure(collector); + this.assertExactlyOneFailure(collector); } @Test @@ -79,13 +120,13 @@ public void should_continue_collecting_after_failing_verification() { verify(methods).byteReturningMethod(); verify(methods).simpleMethod(); - this.assertAtLeastOneFailure(collector); + this.assertExactlyOneFailure(collector); } - private void assertAtLeastOneFailure(VerificationCollector collector) { + private void assertExactlyOneFailure(VerificationCollector collector) { try { collector.collectAndReport(); - fail(); + failBecauseExceptionWasNotThrown(MockitoAssertionError.class); } catch (MockitoAssertionError error) { assertThat(error).hasMessageContaining("1. Wanted but not invoked:"); assertThat(error.getMessage()).doesNotContain("2."); @@ -97,8 +138,11 @@ public void should_invoke_collector_rule_after_test() { JUnitCore runner = new JUnitCore(); Result result = runner.run(VerificationCollectorRuleInner.class); - assertThat(result.getFailureCount()).isEqualTo(1); - assertThat(result.getFailures().get(0).getMessage()).contains("1. Wanted but not invoked:"); + assertThat(result.getFailureCount()).as("failureCount").isEqualTo(2); + assertThat(result.getFailures().get(0).getMessage()).as("failure1").contains("1. Wanted but not invoked:"); + assertThat(result.getFailures().get(1).getMessage()).as("failure2") + .contains("1. Argument(s) are different! Wanted:") + .contains("2. Wanted but not invoked:"); } // This class is picked up when running a test suite using an IDE. It fails on purpose. @@ -121,5 +165,14 @@ public void should_not_fail() { verify(methods).simpleMethod(); } + + @Test + public void should_fail_with_args() { + IMethods methods = mock(IMethods.class); + methods.intArgumentMethod(8); + + verify(methods).intArgumentMethod(9); + verify(methods).byteReturningMethod(); + } } } diff --git a/src/test/java/org/mockitousage/serialization/AcrossClassLoaderSerializationTest.java b/src/test/java/org/mockitousage/serialization/AcrossClassLoaderSerializationTest.java index 240cf7d027..f8a262d15d 100644 --- a/src/test/java/org/mockitousage/serialization/AcrossClassLoaderSerializationTest.java +++ b/src/test/java/org/mockitousage/serialization/AcrossClassLoaderSerializationTest.java @@ -33,7 +33,7 @@ public void check_that_mock_can_be_serialized_in_a_classloader_and_deserialized_ byte[] bytes = create_mock_and_serialize_it_in_class_loader_A(); Object the_deserialized_mock = read_stream_and_deserialize_it_in_class_loader_B(bytes); - assertThat(the_deserialized_mock.getClass().getName()).startsWith("org.mockito.codegenAClassToBeMockedInThisTestOnlyAndInCallablesOnly"); + assertThat(the_deserialized_mock.getClass().getName()).startsWith("org.mockito.codegen.AClassToBeMockedInThisTestOnlyAndInCallablesOnly"); } private Object read_stream_and_deserialize_it_in_class_loader_B(byte[] bytes) throws Exception { diff --git a/src/test/java/org/mockitousage/serialization/DeepStubsSerializableTest.java b/src/test/java/org/mockitousage/serialization/DeepStubsSerializableTest.java index 3294614c24..c26e8d8b57 100644 --- a/src/test/java/org/mockitousage/serialization/DeepStubsSerializableTest.java +++ b/src/test/java/org/mockitousage/serialization/DeepStubsSerializableTest.java @@ -11,7 +11,11 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; import static org.mockitoutil.SimpleSerializationUtil.serializeAndBack; public class DeepStubsSerializableTest { @@ -45,7 +49,7 @@ public void should_serialize_and_deserialize_parameterized_class_mocked_with_dee assertThat(deserialized_deep_stub.iterator().next().add("yes")).isEqualTo(true); } - @Test(expected = ClassCastException.class) + @Test public void should_discard_generics_metadata_when_serialized_then_disabling_deep_stubs_with_generics() throws Exception { // given ListContainer deep_stubbed = mock(ListContainer.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS).serializable()); @@ -53,10 +57,14 @@ public void should_discard_generics_metadata_when_serialized_then_disabling_deep ListContainer deserialized_deep_stub = serializeAndBack(deep_stubbed); - // when stubbing on a deserialized mock - when(deserialized_deep_stub.iterator().next().get(42)).thenReturn("no"); - - // then revert to the default RETURNS_DEEP_STUBS and the code will raise a ClassCastException + try { + // when stubbing on a deserialized mock + // then revert to the default RETURNS_DEEP_STUBS and the code will raise a ClassCastException + when(deserialized_deep_stub.iterator().next().get(42)).thenReturn("no"); + fail("Expected an exception to be thrown as deep stubs and serialization does not play well together"); + } catch (NullPointerException e) { + assertThat(e).hasMessage(null); + } } static class SampleClass implements Serializable { diff --git a/src/test/java/org/mockitousage/serialization/StrictStubsSerializableTest.java b/src/test/java/org/mockitousage/serialization/StrictStubsSerializableTest.java new file mode 100644 index 0000000000..9d786695fc --- /dev/null +++ b/src/test/java/org/mockitousage/serialization/StrictStubsSerializableTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.serialization; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.Serializable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; +import static org.mockitoutil.SimpleSerializationUtil.serializeAndBack; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class StrictStubsSerializableTest { + + @Mock(serializable = true) private SampleClass sampleClass; + + @Test + public void should_serialize_and_deserialize_mock_created_with_serializable_and_strict_stubs() throws Exception { + // given + when(sampleClass.isFalse()).thenReturn(true); + + // when + SampleClass deserializedSample = serializeAndBack(sampleClass); + // to satisfy strict stubbing + deserializedSample.isFalse(); + verify(deserializedSample).isFalse(); + verify(sampleClass, never()).isFalse(); + + // then + assertThat(deserializedSample.isFalse()).isEqualTo(true); + assertThat(sampleClass.isFalse()).isEqualTo(true); + } + + static class SampleClass implements Serializable { + + boolean isFalse() { + return false; + } + } +} diff --git a/src/test/java/org/mockitousage/stubbing/DeepStubbingTest.java b/src/test/java/org/mockitousage/stubbing/DeepStubbingTest.java index df41f062fb..0e574f0c5e 100644 --- a/src/test/java/org/mockitousage/stubbing/DeepStubbingTest.java +++ b/src/test/java/org/mockitousage/stubbing/DeepStubbingTest.java @@ -224,7 +224,7 @@ public void withPatternPrimitive() throws Exception { Person person = mock(Person.class, RETURNS_DEEP_STUBS); @Test - public void shouldStubbingBasicallyWorkFine() throws Exception { + public void shouldStubbingBasicallyWorkFine() { //given given(person.getAddress().getStreet().getName()).willReturn("Norymberska"); @@ -236,7 +236,7 @@ public void shouldStubbingBasicallyWorkFine() throws Exception { } @Test - public void shouldVerificationBasicallyWorkFine() throws Exception { + public void shouldVerificationBasicallyWorkFine() { //given person.getAddress().getStreet().getName(); @@ -245,7 +245,7 @@ public void shouldVerificationBasicallyWorkFine() throws Exception { } @Test - public void verification_work_with_argument_Matchers_in_nested_calls() throws Exception { + public void verification_work_with_argument_Matchers_in_nested_calls() { //given person.getAddress("111 Mock Lane").getStreet(); person.getAddress("111 Mock Lane").getStreet(Locale.ITALIAN).getName(); @@ -257,7 +257,7 @@ public void verification_work_with_argument_Matchers_in_nested_calls() throws Ex } @Test - public void deep_stub_return_same_mock_instance_if_invocation_matchers_matches() throws Exception { + public void deep_stub_return_same_mock_instance_if_invocation_matchers_matches() { when(person.getAddress(anyString()).getStreet().getName()).thenReturn("deep"); person.getAddress("the docks").getStreet().getName(); @@ -270,7 +270,7 @@ public void deep_stub_return_same_mock_instance_if_invocation_matchers_matches() } @Test - public void times_never_atLeast_atMost_verificationModes_should_work() throws Exception { + public void times_never_atLeast_atMost_verificationModes_should_work() { when(person.getAddress(anyString()).getStreet().getName()).thenReturn("deep"); person.getAddress("the docks").getStreet().getName(); @@ -285,7 +285,7 @@ public void times_never_atLeast_atMost_verificationModes_should_work() throws Ex @Test - public void inOrder_only_work_on_the_very_last_mock_but_it_works() throws Exception { + public void inOrder_only_work_on_the_very_last_mock_but_it_works() { when(person.getAddress(anyString()).getStreet().getName()).thenReturn("deep"); when(person.getAddress(anyString()).getStreet(Locale.ITALIAN).getName()).thenReturn("deep"); when(person.getAddress(anyString()).getStreet(Locale.CHINESE).getName()).thenReturn("deep"); @@ -307,7 +307,7 @@ public void inOrder_only_work_on_the_very_last_mock_but_it_works() throws Except } @Test - public void verificationMode_only_work_on_the_last_returned_mock() throws Exception { + public void verificationMode_only_work_on_the_last_returned_mock() { // 1st invocation on Address mock (stubbing) when(person.getAddress("the docks").getStreet().getName()).thenReturn("deep"); @@ -328,7 +328,7 @@ public void verificationMode_only_work_on_the_last_returned_mock() throws Except } @Test - public void shouldFailGracefullyWhenClassIsFinal() throws Exception { + public void shouldFailGracefullyWhenClassIsFinal() { //when FinalClass value = new FinalClass(); given(person.getFinalClass()).willReturn(value); diff --git a/src/test/java/org/mockitousage/stubbing/SmartNullsStubbingTest.java b/src/test/java/org/mockitousage/stubbing/SmartNullsStubbingTest.java index 9b3dc868d1..8e27f73100 100644 --- a/src/test/java/org/mockitousage/stubbing/SmartNullsStubbingTest.java +++ b/src/test/java/org/mockitousage/stubbing/SmartNullsStubbingTest.java @@ -42,6 +42,15 @@ public void shouldSmartNPEPointToUnstubbedCall() throws Exception { } } + @Test + public void should_not_throw_NPE_when_verifying_with_returns_smart_nulls() { + Foo mock = mock(Foo.class, RETURNS_SMART_NULLS); + + when(mock.returnsFromArg(null)).thenReturn("Does not fail."); + + assertThat((Object) mock.returnsFromArg(null)).isEqualTo("Does not fail."); + } + interface Bar { void boo(); } @@ -59,6 +68,8 @@ Bar getBarWithParams(int x, String y) { return null; } + T returnsFromArg(T arg) { return arg; } + void boo() {} } diff --git a/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java index 6ec32893a6..ed732c8089 100644 --- a/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java +++ b/subprojects/module-test/src/test/java/org/mockito/moduletest/ModuleHandlingTest.java @@ -4,6 +4,7 @@ */ package org.mockito.moduletest; +import java.util.concurrent.locks.Lock; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassInjector; @@ -91,6 +92,34 @@ public void can_define_class_in_open_reading_module() throws Exception { } } + @Test + public void can_define_class_in_open_java_util_module() throws Exception { + assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(false)); + + Path jar = modularJar(true, true, true); + ModuleLayer layer = layer(jar, true); + + ClassLoader loader = layer.findLoader("mockito.test"); + Class type = loader.loadClass("java.util.concurrent.locks.Lock"); + + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader); + try { + Class mockito = loader.loadClass(Mockito.class.getName()); + @SuppressWarnings("unchecked") + Lock mock = (Lock) mockito.getMethod("mock", Class.class).invoke(null, type); + Object stubbing = mockito.getMethod("when", Object.class).invoke(null, mock.tryLock()); + loader.loadClass(OngoingStubbing.class.getName()).getMethod("thenReturn", Object.class).invoke(stubbing, true); + + boolean relocated = !Boolean.getBoolean("org.mockito.internal.noUnsafeInjection") && ClassInjector.UsingReflection.isAvailable(); + String prefix = relocated ? "org.mockito.codegen.Lock$MockitoMock$" : "java.util.concurrent.locks.Lock$MockitoMock$"; + assertThat(mock.getClass().getName()).startsWith(prefix); + assertThat(mock.tryLock()).isEqualTo(true); + } finally { + Thread.currentThread().setContextClassLoader(contextLoader); + } + } + @Test public void inline_mock_maker_can_mock_closed_modules() throws Exception { assumeThat(Plugins.getMockMaker() instanceof InlineByteBuddyMockMaker, is(true)); diff --git a/version.properties b/version.properties index 21052c6954..0a877c4d56 100644 --- a/version.properties +++ b/version.properties @@ -2,7 +2,7 @@ version=2.25.0 #Previous version used to generate release notes delta -previousVersion=2.24.1 +previousVersion=2.24.10 #Not published currently, see https://github.com/mockito/mockito/issues/962 mockito.testng.version=1.0