Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automatically detect class to mock #2779

Merged
merged 6 commits into from Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 62 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,26 @@
* 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>
*
* <p>
* Starting from 4.9.0, there is a shorter form of creating a mock or spy.
* </p>
*
asolntsev marked this conversation as resolved.
Show resolved Hide resolved
* 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>
* P.S. Works only if you assign result of {@code mock()} or {@code spy()} to a variable or field.
* Only then Java compiler can know the type of that variable.
asolntsev marked this conversation as resolved.
Show resolved Hide resolved
* </p>
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -1901,6 +1926,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
szpak marked this conversation as resolved.
Show resolved Hide resolved
* @since 4.9.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too late, I know, but shouldn't it have been:

Suggested change
* @since 4.9.0
* @since 4.10.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.");
szpak marked this conversation as resolved.
Show resolved Hide resolved
}
return mock(getClassOf(reified), withSettings());
}

/**
* Creates mock object of given class or interface.
* <p>
Expand Down Expand Up @@ -2115,6 +2157,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())
}
}