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

Fixes #2548 : Makes InOrder able to verify static methods #2549

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
35 changes: 35 additions & 0 deletions src/main/java/org/mockito/InOrder.java
Expand Up @@ -4,6 +4,8 @@
*/
package org.mockito;

import static org.mockito.Mockito.times;

import org.mockito.verification.VerificationMode;

/**
Expand Down Expand Up @@ -62,6 +64,39 @@ public interface InOrder {
*/
<T> T verify(T mock, VerificationMode mode);

/**
* Verifies static interaction in order, with exactly one number of invocations.
*
* @see #verify(MockedStatic, MockedStatic.Verification, VerificationMode)
*/
default void verify(MockedStatic<?> mockedStatic, MockedStatic.Verification verification) {
verify(mockedStatic, verification, times(1));
}

/**
* Verifies static interaction in order. E.g:
*
* <pre class="code"><code class="java">
* try (MockedStatic<Foo> mocked = mockStatic(Foo.class)) {
* InOrder inOrder = inOrder(Foo.class);
*
* mocked.when(Foo::firstMethod).thenReturn("first");
* mocked.when(Foo::secondMethod).thenReturn("second");
*
* assertEquals("first", Foo.firstMethod());
* assertEquals("second", Foo.secondMethod());
*
* inOrder.verify(mocked, Foo::firstMethod, times(1));
* inOrder.verify(mocked, Foo::secondMethod, atLeastOnce());
* }
* </code></pre>
*
* @param mockedStatic static mock to be verified
* @param verification verification to be verified
* @param mode for example times(x) or atLeastOnce()
*/
void verify(MockedStatic<?> mockedStatic, MockedStatic.Verification verification, VerificationMode mode);

/**
* Verifies that no more interactions happened <b>in order</b>.
* Different from {@link Mockito#verifyNoMoreInteractions(Object...)} because the order of verification matters.
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/org/mockito/internal/InOrderImpl.java
Expand Up @@ -10,6 +10,7 @@
import java.util.List;

import org.mockito.InOrder;
import org.mockito.MockedStatic;
import org.mockito.MockingDetails;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.verification.InOrderContextImpl;
Expand Down Expand Up @@ -61,14 +62,28 @@ public <T> T verify(T mock, VerificationMode mode) {
}
if (mode instanceof VerificationWrapper) {
return mockitoCore.verify(
mock, new VerificationWrapperInOrderWrapper((VerificationWrapper) mode, this));
mock,
new VerificationWrapperInOrderWrapper((VerificationWrapper<?>) mode, this));
} else if (!(mode instanceof VerificationInOrderMode)) {
throw new MockitoException(
mode.getClass().getSimpleName() + " is not implemented to work with InOrder");
}
return mockitoCore.verify(mock, new InOrderWrapper((VerificationInOrderMode) mode, this));
}

@Override
public void verify(MockedStatic<?> mockedStatic, MockedStatic.Verification verification, VerificationMode mode) {
if (mode instanceof VerificationWrapper) {
mockedStatic.verify(verification,
new VerificationWrapperInOrderWrapper((VerificationWrapper<?>) mode, this));
} else if (mode instanceof VerificationInOrderMode) {
mockedStatic.verify(verification, new InOrderWrapper((VerificationInOrderMode) mode, this));
} else {
throw new MockitoException(
mode.getClass().getSimpleName() + " is not implemented to work with InOrder");
}
}

// We can't use `this.mocksToBeVerifiedInOrder.contains`, since that in turn calls `.equals` on
// the mock. Since mocks can be spies and spies get their real equals method calls called, the
// result is that Mockito incorrectly would register an invocation on a mock. This normally
Expand Down
@@ -0,0 +1,191 @@
/*
* Copyright (c) 2022 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitoinline;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;

import java.util.function.Consumer;

import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.MockedStatic;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.exceptions.verification.VerificationInOrderFailure;
import org.mockito.verification.VerificationMode;

public class InOrderVerificationTest {
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved

@Test
public void shouldVerifyStaticMethods() {
mockedStaticTest(mockedStatic -> {
// given
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();
StaticContext.secondMethod(0);

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod);
inOrder.verify(mockedStatic, () -> StaticContext.secondMethod(0));
});
}

@Test
public void shouldVerifyStaticAndInstanceMethods() {
mockedStaticTest(mockedStatic -> {
// given
StaticContext mocked = mock(StaticContext.class);
InOrder inOrder = inOrder(mocked, StaticContext.class);

// when
StaticContext.firstMethod();
mocked.instanceMethod();
StaticContext.secondMethod(10);

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod);
inOrder.verify(mocked).instanceMethod();
inOrder.verify(mockedStatic, () -> StaticContext.secondMethod(10));
});
}

@Test
public void shouldVerifyStaticMethodsWithSimpleAndWrapperModes() {
mockedStaticTest(mockedStatic -> {
// given
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();
StaticContext.firstMethod();
StaticContext.secondMethod(0);

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod, times(2));
inOrder.verify(mockedStatic, () -> StaticContext.secondMethod(0), timeout(100).atLeastOnce());
});
}

@Test(expected = MockitoException.class)
public void shouldThrowExceptionWhenModeIsUnsupported() {
mockedStaticTest(mockedStatic -> {
// given
VerificationMode unsupportedMode = data -> { };
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod, unsupportedMode);
});
}

@Test(expected = VerificationInOrderFailure.class)
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved
public void shouldThrowExceptionWhenOrderIsWrong() {
mockedStaticTest(mockedStatic -> {
// given
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();
StaticContext.secondMethod(0);

// then
inOrder.verify(mockedStatic, () -> StaticContext.secondMethod(0));
inOrder.verify(mockedStatic, StaticContext::firstMethod);
});
}

@Test(expected = VerificationInOrderFailure.class)
public void shouldThrowExceptionWhenNoMoreInteractionsInvokedButThereAre() {
mockedStaticTest(mockedStatic -> {
// given
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();
StaticContext.secondMethod(0);

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod);
inOrder.verifyNoMoreInteractions();
});
}

@Test(expected = VerificationInOrderFailure.class)
public void shouldThrowExceptionWhenNoMoreInteractionsInvokedWithoutVerifyingStaticMethods() {
mockedStaticTest(ignored -> {
// given
StaticContext mocked = mock(StaticContext.class);
InOrder inOrder = inOrder(StaticContext.class, mocked);

// when
mocked.instanceMethod();
StaticContext.firstMethod();

// then
inOrder.verify(mocked).instanceMethod();
inOrder.verifyNoMoreInteractions();
});
}

@Test(expected = NotAMockException.class)
public void shouldThrowExceptionWhenClassIsNotMocked() {
InOrder ignored = inOrder(StaticContext.class);
}

@Test
public void shouldVerifyStaticMethodsWithoutInterferingWithMocking() {
mockedStaticTest(mockedStatic -> {
// given
InOrder inOrder = inOrder(StaticContext.class);
Exception expected = new RuntimeException();
Exception actual = null;
mockedStatic.when(StaticContext::firstMethod).thenThrow(expected);

// when
try {
StaticContext.firstMethod();
} catch (Exception e) {
actual = e;
mirkoalicastro marked this conversation as resolved.
Show resolved Hide resolved
}

// then
if (actual != expected) {
fail("Unable to mock static method");
}
inOrder.verify(mockedStatic, StaticContext::firstMethod);
inOrder.verifyNoMoreInteractions();
});
}

private void mockedStaticTest(Consumer<MockedStatic<StaticContext>> body) {
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
body.accept(mockedStatic);
}
}

private static class StaticContext {
static void firstMethod() {
fail("firstMethod should be mocked");
}

static void secondMethod(int n) {
fail("secondMethod should be mocked but was invoked with argument " + n);
}

void instanceMethod() {
fail("instanceMethod should be mocked");
}
}
}