Skip to content

Commit

Permalink
Add Mockito.{mock,spy} that take no Class argument (#2779)
Browse files Browse the repository at this point in the history
This commit adds method `Mockito.mock()` as a shorter alternative for `Mockito.mock(Class)`.
When the result of this call is assigned to a variable/field with an explicit type, java will detect
the needed class automatically.

Co-authored-by: Tim van der Lippe <TimvdLippe@users.noreply.github.com>
  • Loading branch information
asolntsev and TimvdLippe committed Nov 14, 2022
1 parent 0052e2f commit 3faa002
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 1 deletion.
60 changes: 59 additions & 1 deletion src/main/java/org/mockito/Mockito.java
Expand Up @@ -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;

Expand Down Expand Up @@ -107,6 +111,7 @@
* <a href="#51">51. New API for marking classes as unmockable (Since 4.1.0)</a><br/>
* <a href="#52">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.6.0)</a><br/>
* <a href="#53">53. Specifying mock maker for individual mocks (Since 4.8.0)</a><br/>
* <a href="#54">54. Mocking/spying without specifying class (Since 4.9.0)</a><br/>
* </b>
*
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
Expand Down Expand Up @@ -1639,6 +1644,23 @@
* Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));
* </code></pre>
*
* <h3 id="54">54. <a class="meaningful_link" href="#mock_without_class" name="mock_without_class">
* Mocking/spying without specifying class</a> (Since 4.9.0)</h3>
*
* 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()} <strong>without parameters</strong>:
*
* <pre class="code"><code class="java">
* Foo foo = Mockito.mock();
* Bar bar = Mockito.spy();
* </code></pre>
*
* Mockito will automatically detect the needed class.
* <p>
* 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.
* </p>
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -1901,6 +1923,23 @@ public class Mockito extends ArgumentMatchers {
*/
public static final Answer<Object> RETURNS_SELF = Answers.RETURNS_SELF;

/**
* Creates mock object of requested class or interface.
* <p>
* 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> 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.
* <p>
Expand Down Expand Up @@ -2115,6 +2154,25 @@ public static <T> T spy(Class<T> 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> 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 <T> Class<T> getClassOf(T[] array) {
return (Class<T>) 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
Expand Down
40 changes: 40 additions & 0 deletions src/test/java/org/mockito/MockitoTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -133,4 +134,43 @@ public void shouldStartingMockSettingsContainDefaultBehavior() {
// when / then
assertThat(settings.getDefaultAnswer()).isEqualTo(Mockito.RETURNS_DEFAULTS);
}

@Test
@SuppressWarnings({"DoNotMock", "DoNotMockAutoValue"})
public void automaticallyDetectsClassToMock() {
List<String> 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<String> 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");
}
}
Expand Up @@ -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())
}
}

0 comments on commit 3faa002

Please sign in to comment.