Skip to content

Commit

Permalink
Fixes #2548: make InOrder able to verify static methods (#2549)
Browse files Browse the repository at this point in the history
  • Loading branch information
mirkoalicastro committed Feb 16, 2022
1 parent e0a25cb commit 02d6356
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 1 deletion.
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 {
@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");
}
}
}

0 comments on commit 02d6356

Please sign in to comment.