diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java index a221376287..31eed046cf 100644 --- a/src/main/java/org/mockito/Mockito.java +++ b/src/main/java/org/mockito/Mockito.java @@ -33,7 +33,11 @@ import org.mockito.stubbing.OngoingStubbing; import org.mockito.stubbing.Stubber; import org.mockito.stubbing.VoidAnswer1; -import org.mockito.verification.*; +import org.mockito.verification.After; +import org.mockito.verification.Timeout; +import org.mockito.verification.VerificationAfterDelay; +import org.mockito.verification.VerificationMode; +import org.mockito.verification.VerificationWithTimeout; import java.util.function.Function; @@ -107,6 +111,7 @@ * 51. New API for marking classes as unmockable (Since 4.1.0)
* 52. New strictness attribute for @Mock annotation and MockSettings.strictness() methods (Since 4.6.0)
* 53. Specifying mock maker for individual mocks (Since 4.8.0)
+ * 54. Mocking/spying without specifying class (Since 4.9.0)
* * *

0. Migrating to Mockito 2

@@ -1639,6 +1644,23 @@ * Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS)); * * + *

54. + * Mocking/spying without specifying class (Since 4.9.0)

+ * + * Instead of calling method {@link Mockito#mock(Class)} or {@link Mockito#spy(Class)} with Class parameter, you can now + * now call method {@code mock()} or {@code spy()} without parameters: + * + *

+ *   Foo foo = Mockito.mock();
+ *   Bar bar = Mockito.spy();
+ * 
+ * + * Mockito will automatically detect the needed class. + *

+ * It works only if you assign result of {@code mock()} or {@code spy()} to a variable or field with an explicit type. + * With an implicit type, the Java compiler is unable to automatically determine the type of a mock and you need + * to pass in the {@code Class} explicitly. + *

*/ @CheckReturnValue @SuppressWarnings("unchecked") @@ -1901,6 +1923,23 @@ public class Mockito extends ArgumentMatchers { */ public static final Answer RETURNS_SELF = Answers.RETURNS_SELF; + /** + * Creates mock object of requested class or interface. + *

+ * See examples in javadoc for {@link Mockito} class + * + * @param reified don't pass any values to it. It's a trick to detect the class/interface you want to mock. + * @return mock object + * @since 4.9.0 + */ + public static T mock(T... reified) { + if (reified.length > 0) { + throw new IllegalArgumentException( + "Please don't pass any values here. Java will detect class automagically."); + } + return mock(getClassOf(reified), withSettings()); + } + /** * Creates mock object of given class or interface. *

@@ -2115,6 +2154,25 @@ public static T spy(Class classToSpy) { classToSpy, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS)); } + /** + * Please refer to the documentation of {@link #spy(Class)}. + * + * @param reified don't pass any values to it. It's a trick to detect the class/interface you want to mock. + * @return spy object + * @since 4.9.0 + */ + public static T spy(T... reified) { + if (reified.length > 0) { + throw new IllegalArgumentException( + "Please don't pass any values here. Java will detect class automagically."); + } + return spy(getClassOf(reified)); + } + + private static Class getClassOf(T[] array) { + return (Class) array.getClass().getComponentType(); + } + /** * Creates a thread-local mock controller for all static methods of the given class or interface. * The returned object's {@link MockedStatic#close()} method must be called upon completing the diff --git a/src/test/java/org/mockito/MockitoTest.java b/src/test/java/org/mockito/MockitoTest.java index 8455011bb2..168acb8f40 100644 --- a/src/test/java/org/mockito/MockitoTest.java +++ b/src/test/java/org/mockito/MockitoTest.java @@ -4,6 +4,7 @@ */ package org.mockito; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.times; @@ -133,4 +134,43 @@ public void shouldStartingMockSettingsContainDefaultBehavior() { // when / then assertThat(settings.getDefaultAnswer()).isEqualTo(Mockito.RETURNS_DEFAULTS); } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void automaticallyDetectsClassToMock() { + List mock = Mockito.mock(); + Mockito.when(mock.size()).thenReturn(42); + assertThat(mock.size()).isEqualTo(42); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void newMockMethod_shouldNotBeCalledWithParameters() { + assertThatThrownBy( + () -> { + Mockito.mock(asList("1", "2"), asList("3", "4")); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Please don't pass any values here"); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void automaticallyDetectsClassToSpy() { + List mock = Mockito.spy(); + Mockito.when(mock.size()).thenReturn(42); + assertThat(mock.size()).isEqualTo(42); + assertThat(mock.get(0)).isNull(); + } + + @Test + @SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"}) + public void newSpyMethod_shouldNotBeCalledWithParameters() { + assertThatThrownBy( + () -> { + Mockito.spy(asList("1", "2"), asList("3", "4")); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Please don't pass any values here"); + } } diff --git a/subprojects/kotlinTest/src/test/kotlin/org/mockito/kotlin/InlineClassTest.kt b/subprojects/kotlinTest/src/test/kotlin/org/mockito/kotlin/InlineClassTest.kt index a1a64ce0b0..daa60a9a9a 100644 --- a/subprojects/kotlinTest/src/test/kotlin/org/mockito/kotlin/InlineClassTest.kt +++ b/subprojects/kotlinTest/src/test/kotlin/org/mockito/kotlin/InlineClassTest.kt @@ -339,4 +339,14 @@ class InlineClassTest { verify(mock).returnsResult() } + + @Test + @SuppressWarnings("DoNotMock", "DoNotMockAutoValue") + fun automaticallyDetectsClassToMock() { + val mock: WithResult = mock() + + `when`(mock.returnsResult()).thenReturn(Result.success("OK")) + + assertEquals("OK", mock.returnsResult().getOrNull()) + } }