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 all 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
38 changes: 38 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,42 @@ 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
22 changes: 21 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,33 @@ 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,213 @@
/*
* Copyright (c) 2022 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitoinline;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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 org.assertj.core.api.Assert;
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() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// 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() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// 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() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// 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
public void shouldThrowExceptionWhenModeIsUnsupported() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// given
VerificationMode unsupportedMode = data -> { };
InOrder inOrder = inOrder(StaticContext.class);

// when
StaticContext.firstMethod();

// then
assertThatThrownBy(() ->
inOrder.verify(mockedStatic, StaticContext::firstMethod, unsupportedMode)
).isInstanceOf(MockitoException.class);
}
}

@Test
public void shouldThrowExceptionWhenOrderIsWrong() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// given
InOrder inOrder = inOrder(StaticContext.class);

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

// then
assertThatThrownBy(() -> {
inOrder.verify(mockedStatic, () -> StaticContext.secondMethod(0));
inOrder.verify(mockedStatic, StaticContext::firstMethod);
}).isInstanceOf(VerificationInOrderFailure.class);
}
}

@Test
public void shouldThrowExceptionWhenNoMoreInteractionsInvokedButThereAre() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// given
InOrder inOrder = inOrder(StaticContext.class);

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

// then
inOrder.verify(mockedStatic, StaticContext::firstMethod);
assertThatThrownBy(inOrder::verifyNoMoreInteractions)
.isInstanceOf(VerificationInOrderFailure.class);
}
}

@Test
public void shouldThrowExceptionWhenNoMoreInteractionsInvokedWithoutVerifyingStaticMethods() {
try (MockedStatic<StaticContext> ignored = mockStatic(StaticContext.class)) {
// given
StaticContext mocked = mock(StaticContext.class);
InOrder inOrder = inOrder(StaticContext.class, mocked);

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

// then
inOrder.verify(mocked).instanceMethod();
assertThatThrownBy(inOrder::verifyNoMoreInteractions)
.isInstanceOf(VerificationInOrderFailure.class);
}
}

@Test
public void shouldThrowExceptionWhenClassIsNotMocked() {
assertThatThrownBy(
() -> inOrder(StaticContext.class)
).isInstanceOf(NotAMockException.class);
}

@Test
public void shouldVerifyStaticMethodsWithoutInterferingWithMocking() {
try (MockedStatic<StaticContext> mockedStatic = mockStatic(StaticContext.class)) {
// given
InOrder inOrder = inOrder(StaticContext.class);
Exception expected = new RuntimeException();
mockedStatic.when(StaticContext::firstMethod).thenThrow(expected);

// when
Assert<?, ?> actual = assertThatThrownBy(StaticContext::firstMethod);

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

@Test
public void shouldThrowExceptionWhenVerifyUsingInOrderWithoutValidClass() {
try (MockedStatic<StaticContext> mockedStaticContext = mockStatic(StaticContext.class)) {
try (MockedStatic<AnotherStaticContext> mockedAnotherStaticContext = mockStatic(AnotherStaticContext.class)) {
// given
InOrder inOrder = inOrder(AnotherStaticContext.class);

// when
mockedAnotherStaticContext.when(AnotherStaticContext::otherMethod).thenReturn("mocked value");
StaticContext.firstMethod();

// then
assertThat(AnotherStaticContext.otherMethod())
.isEqualTo("mocked value");
inOrder.verify(mockedAnotherStaticContext, AnotherStaticContext::otherMethod);
assertThatThrownBy(() -> inOrder.verify(mockedStaticContext, StaticContext::firstMethod))
.isInstanceOf(VerificationInOrderFailure.class);
}
}
}

private static class AnotherStaticContext {
static String otherMethod() {
throw new AssertionError("otherMethod should be mocked");
}
}

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");
}
}
}