Skip to content

Commit

Permalink
Mockito \#1013: Defines and implements API for static mocking.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Jul 2, 2020
1 parent 4fd405d commit dd3e913
Show file tree
Hide file tree
Showing 66 changed files with 1,294 additions and 198 deletions.
11 changes: 9 additions & 2 deletions src/main/java/org/mockito/Captor.java
Expand Up @@ -15,9 +15,16 @@
*
* @Captor ArgumentCaptor<AsyncCallback<Foo>> captor;
*
* private AutoCloseable closeable;
*
* @Before
* public void init(){
* MockitoAnnotations.initMocks(this);
* public void open() {
* MockitoAnnotations.openMocks(this);
* }
*
* @After
* public void release() throws Exception {
* closeable.close();
* }
*
* @Test public void shouldDoSomethingUseful() {
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/org/mockito/InjectMocks.java
Expand Up @@ -71,8 +71,14 @@
*
* public class SampleBaseTestCase {
*
* @Before public void initMocks() {
* MockitoAnnotations.initMocks(this);
* private AutoCloseable closeable;
*
* @Before public void openMocks() {
* closeable = MockitoAnnotations.openMocks(this);
* }
*
* @After public void releaseMocks() throws Exception {
* closeable.close();
* }
* }
* </code></pre>
Expand Down Expand Up @@ -141,11 +147,11 @@
* </p>
*
* <p>
* <strong><code>MockitoAnnotations.initMocks(this)</code></strong> method has to be called to initialize annotated objects.
* In above example, <code>initMocks()</code> is called in &#064;Before (JUnit4) method of test's base class.
* For JUnit3 <code>initMocks()</code> can go to <code>setup()</code> method of a base class.
* <strong>Instead</strong> you can also put initMocks() in your JUnit runner (&#064;RunWith) or use the built-in
* {@link MockitoJUnitRunner}.
* <strong><code>MockitoAnnotations.openMocks(this)</code></strong> method has to be called to initialize annotated objects.
* In above example, <code>openMocks()</code> is called in &#064;Before (JUnit4) method of test's base class.
* For JUnit3 <code>openMocks()</code> can go to <code>setup()</code> method of a base class.
* <strong>Instead</strong> you can also put openMocks() in your JUnit runner (&#064;RunWith) or use the built-in
* {@link MockitoJUnitRunner}. Also, make sure to release any mocks after disposing your test class with a corresponding hook.
* </p>
*
* <p>
Expand All @@ -155,7 +161,7 @@
*
* @see Mock
* @see Spy
* @see MockitoAnnotations#initMocks(Object)
* @see MockitoAnnotations#openMocks(Object)
* @see MockitoJUnitRunner
* @since 1.8.3
*/
Expand Down
23 changes: 15 additions & 8 deletions src/main/java/org/mockito/Mock.java
Expand Up @@ -23,6 +23,7 @@
* <li>Minimizes repetitive mock creation code.</li>
* <li>Makes the test class more readable.</li>
* <li>Makes the verification error easier to read because the <b>field name</b> is used to identify the mock.</li>
* <li>Automatically detects static mocks of type {@link MockedStatic} and infers the static mock type of the type parameter.</li>
* </ul>
*
* <pre class="code"><code class="java">
Expand All @@ -43,24 +44,30 @@
*
* public class SampleBaseTestCase {
*
* &#064;Before public void initMocks() {
* MockitoAnnotations.initMocks(this);
* private AutoCloseable closeable;
*
* &#064;Before public void openMocks() {
* closeable = MockitoAnnotations.openMocks(this);
* }
*
* &#064;After public void releaseMocks() throws Exception {
* closeable.close();
* }
* }
* </code></pre>
*
* <p>
* <strong><code>MockitoAnnotations.initMocks(this)</code></strong> method has to be called to initialize annotated objects.
* In above example, <code>initMocks()</code> is called in &#064;Before (JUnit4) method of test's base class.
* For JUnit3 <code>initMocks()</code> can go to <code>setup()</code> method of a base class.
* <strong>Instead</strong> you can also put initMocks() in your JUnit runner (&#064;RunWith) or use the built-in
* {@link MockitoJUnitRunner}.
* <strong><code>MockitoAnnotations.openMocks(this)</code></strong> method has to be called to initialize annotated objects.
* In above example, <code>openMocks()</code> is called in &#064;Before (JUnit4) method of test's base class.
* For JUnit3 <code>openMocks()</code> can go to <code>setup()</code> method of a base class.
* <strong>Instead</strong> you can also put openMocks() in your JUnit runner (&#064;RunWith) or use the built-in
* {@link MockitoJUnitRunner}. Also, make sure to release any mocks after disposing your test class with a corresponding hook.
* </p>
*
* @see Mockito#mock(Class)
* @see Spy
* @see InjectMocks
* @see MockitoAnnotations#initMocks(Object)
* @see MockitoAnnotations#openMocks(Object)
* @see MockitoJUnitRunner
*/
@Target({FIELD, PARAMETER})
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/mockito/MockSettings.java
Expand Up @@ -340,6 +340,21 @@ public interface MockSettings extends Serializable {
@Incubating
<T> MockCreationSettings<T> build(Class<T> typeToMock);

/**
* Creates immutable view of mock settings used later by Mockito, for use within a static mocking.
* Framework integrators can use this method to create instances of creation settings
* and use them in advanced use cases, for example to create invocations with {@link InvocationFactory},
* or to implement custom {@link MockHandler}.
* Since {@link MockCreationSettings} is {@link NotExtensible}, Mockito public API needs a creation method for this type.
*
* @param classToMock class to mock
* @param <T> type to mock
* @return immutable view of mock settings
* @since 2.10.0
*/
@Incubating
<T> MockCreationSettings<T> buildStatic(Class<T> classToMock);

/**
* Lenient mocks bypass "strict stubbing" validation (see {@link Strictness#STRICT_STUBS}).
* When mock is declared as lenient none of its stubbings will be checked for potential stubbing problems such as
Expand Down
209 changes: 209 additions & 0 deletions src/main/java/org/mockito/MockedStatic.java
@@ -0,0 +1,209 @@
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito;

import org.mockito.exceptions.base.MockitoAssertionError;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.creation.StaticMockControl;
import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.listeners.VerificationStartedNotifier;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.stubbing.InvocationContainerImpl;
import org.mockito.internal.verification.MockAwareVerificationMode;
import org.mockito.internal.verification.VerificationDataImpl;
import org.mockito.invocation.Location;
import org.mockito.invocation.MockHandler;
import org.mockito.stubbing.OngoingStubbing;
import org.mockito.verification.VerificationMode;

import static org.mockito.Mockito.times;
import static org.mockito.internal.exceptions.Reporter.missingMethodInvocation;
import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
import static org.mockito.internal.util.MockUtil.getInvocationContainer;
import static org.mockito.internal.util.MockUtil.resetMock;
import static org.mockito.internal.util.StringUtil.join;
import static org.mockito.internal.verification.VerificationModeFactory.noInteractions;
import static org.mockito.internal.verification.VerificationModeFactory.noMoreInteractions;

/**
* Represents an active mock of a type's static methods. The mocking only affects the thread
* on which this static mock was created and it is not safe to use this object from another
* thread. The static mock is released when this object's {@link MockedStatic#close()} method
* is invoked. If this object is never closed, the static mock will remain active on the
* initiating thread. It is therefore recommended to create this object within a try-with-resources
* statement unless when managed explicitly, for example by using a JUnit rule or extension.
* <p>
* If the {@link Mock} annotation is used on fields or method parameters of this type, a static mock
* is created instead of a regular mock. The static mock is activated and released upon completing any
* relevant test.
*
* @param <T> The type being mocked.
*/
@Incubating
public final class MockedStatic<T> implements AutoCloseable {

private final StaticMockControl<T> control;

private boolean closed;

private final Location location = new LocationImpl();

public MockedStatic(StaticMockControl<T> control) {
this.control = control;
}

/**
* See {@link Mockito#when(Object)}.
*/
public <S> OngoingStubbing<S> when(Verification verification) {
assertNotClosed();

try {
verification.apply();
} catch (Throwable ignored) {
}

MockingProgress mockingProgress = mockingProgress();
mockingProgress.stubbingStarted();
@SuppressWarnings("unchecked")
OngoingStubbing<S> stubbing = (OngoingStubbing<S>) mockingProgress.pullOngoingStubbing();
if (stubbing == null) {
mockingProgress.reset();
throw missingMethodInvocation();
}
return stubbing;
}

/**
* See {@link Mockito#verify(Object)}.
*/
public void verify(Verification verification) {
verify(times(1), verification);
}

/**
* See {@link Mockito#verify(Object, VerificationMode)}.
*/
public void verify(VerificationMode mode, Verification verification) {
assertNotClosed();

MockingDetails mockingDetails = Mockito.mockingDetails(control.getType());
MockHandler handler = mockingDetails.getMockHandler();

VerificationStartedNotifier.notifyVerificationStarted(
handler.getMockSettings().getVerificationStartedListeners(), mockingDetails);

MockingProgress mockingProgress = mockingProgress();
VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode);
mockingProgress.verificationStarted(
new MockAwareVerificationMode(
control.getType(), actualMode, mockingProgress.verificationListeners()));

try {
verification.apply();
} catch (MockitoException | MockitoAssertionError e) {
throw e;
} catch (Throwable t) {
throw new MockitoException(
join(
"An unexpected error occurred while verifying a static stub",
"",
"To correctly verify a stub, invoke a single static method of "
+ control.getType().getName()
+ " in the provided lambda.",
"For example, if a method 'sample' was defined, provide a lambda or anonymous class containing the code",
"",
"() -> " + control.getType().getSimpleName() + ".sample()",
"or",
control.getType().getSimpleName() + "::sample"),
t);
}
}

/**
* See {@link Mockito#reset(Object[])}.
*/
public void reset() {
assertNotClosed();

MockingProgress mockingProgress = mockingProgress();
mockingProgress.validateState();
mockingProgress.reset();
mockingProgress.resetOngoingStubbing();

resetMock(control.getType());
}

/**
* See {@link Mockito#clearInvocations(Object[])}.
*/
public void clearInvocations() {
assertNotClosed();

MockingProgress mockingProgress = mockingProgress();
mockingProgress.validateState();
mockingProgress.reset();
mockingProgress.resetOngoingStubbing();

getInvocationContainer(control.getType()).clearInvocations();
}

/**
* {@link Mockito#verifyNoMoreInteractions(Object...)}.
*/
public void verifyNoMoreInteractions() {
assertNotClosed();

mockingProgress().validateState();
InvocationContainerImpl invocations = getInvocationContainer(control.getType());
VerificationDataImpl data = new VerificationDataImpl(invocations, null);
noMoreInteractions().verify(data);
}

/**
* See {@link Mockito#verifyNoInteractions(Object...)}.
*/
public void verifyNoInteractions() {
assertNotClosed();

mockingProgress().validateState();
InvocationContainerImpl invocations = getInvocationContainer(control.getType());
VerificationDataImpl data = new VerificationDataImpl(invocations, null);
noInteractions().verify(data);
}

@Override
public void close() {
assertNotClosed();

closed = true;
control.disable();
}

/**
* Releases this static mock and is non-operational if already released.
*/
public void closeOnDemand() {
if (!closed) {
close();
}
}

private void assertNotClosed() {
if (closed) {
throw new MockitoException(
join(
"The static mock created at",
location.toString(),
"is already resolved and cannot longer be used"));
}
}

@FunctionalInterface
public interface Verification {

void apply() throws Throwable;
}
}

0 comments on commit dd3e913

Please sign in to comment.