diff --git a/src/main/java/org/mockito/InOrder.java b/src/main/java/org/mockito/InOrder.java index 02ea019c01..3cb047a4c4 100644 --- a/src/main/java/org/mockito/InOrder.java +++ b/src/main/java/org/mockito/InOrder.java @@ -4,6 +4,8 @@ */ package org.mockito; +import static org.mockito.Mockito.times; + import org.mockito.verification.VerificationMode; /** @@ -62,6 +64,42 @@ public interface InOrder { */ 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: + * + *

+     * try (MockedStatic 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());
+     * }
+     * 
+ * + * @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 in order. * Different from {@link Mockito#verifyNoMoreInteractions(Object...)} because the order of verification matters. diff --git a/src/main/java/org/mockito/internal/InOrderImpl.java b/src/main/java/org/mockito/internal/InOrderImpl.java index af852e49ae..93f5991af0 100644 --- a/src/main/java/org/mockito/internal/InOrderImpl.java +++ b/src/main/java/org/mockito/internal/InOrderImpl.java @@ -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; @@ -61,7 +62,8 @@ public 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"); @@ -69,6 +71,24 @@ public T verify(T mock, VerificationMode mode) { 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 diff --git a/subprojects/inline/src/test/java/org/mockitoinline/InOrderVerificationTest.java b/subprojects/inline/src/test/java/org/mockitoinline/InOrderVerificationTest.java new file mode 100644 index 0000000000..fb35adc76f --- /dev/null +++ b/subprojects/inline/src/test/java/org/mockitoinline/InOrderVerificationTest.java @@ -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 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 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 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 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 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 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 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 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 mockedStaticContext = mockStatic(StaticContext.class)) { + try (MockedStatic 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"); + } + } +}