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

AdditionalMatchers.and() and or() swap matcher order #3331

Closed
4 of 5 tasks
DidierLoiseau opened this issue Apr 29, 2024 · 5 comments · Fixed by #3335
Closed
4 of 5 tasks

AdditionalMatchers.and() and or() swap matcher order #3331

DidierLoiseau opened this issue Apr 29, 2024 · 5 comments · Fixed by #3335

Comments

@DidierLoiseau
Copy link

Consider the following code:

@ExtendWith(MockitoExtension.class)
public class MockitoTest {
  @Mock MyService myService;

  @Test
  public void test() {
    Mockito.when(
            myService.doSomething(
                and(any(MyPojo.class),
                    argThat(p -> p.id == 1))))
        .thenReturn(1);

    myService.doSomething("hello");
  }

  interface MyService {
    int doSomething(Object pojo);
  }

  static class MyPojo {
    int id = 1;
  }
}

This test throws the following exception:

java.lang.ClassCastException: class java.lang.String cannot be cast to class MockitoTest$MyPojo (java.lang.String is in module java.base of loader 'bootstrap'; MockitoTest$MyPojo is in unnamed module of loader 'app')
	at org.mockito.internal.matchers.And.matches(And.java:23)
	at org.mockito.internal.invocation.TypeSafeMatching.apply(TypeSafeMatching.java:35)
	at org.mockito.internal.invocation.MatcherApplicationStrategy.argsMatch(MatcherApplicationStrategy.java:85)
	at org.mockito.internal.invocation.MatcherApplicationStrategy.forEachMatcherAndArgument(MatcherApplicationStrategy.java:71)
	at org.mockito.internal.invocation.InvocationMatcher.argumentsMatch(InvocationMatcher.java:156)
	at org.mockito.internal.invocation.InvocationMatcher.matches(InvocationMatcher.java:82)
	at org.mockito.internal.stubbing.InvocationContainerImpl.findAnswerFor(InvocationContainerImpl.java:92)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:91)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
	at MockitoTest$MyService$MockitoMock$v3DeXXsQ.doSomething(Unknown Source)
	at MockitoTest.test(MockitoTest.java:23)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)

Debugger inspection of And.matches() shows that m1 is the lambda and m2 is the InstanceOf, as opposed to what was passed to and(). I guess this comes from ArgumentMatcherStorageImpl#reportAnd() and reportOr() which pop m1 before m2, swapping the order.

This is with Mockito 5.11.0, Junit 5.10.2 and Java 11.

check that

@TimvdLippe
Copy link
Contributor

Yes this appears to be a legitimate bug, good catch! Feel free to submit a pull request with a fix and a regression test for it!

@dev-jonghoonpark
Copy link
Contributor

can i try contribute?

@TimvdLippe
Copy link
Contributor

@dev-jonghoonpark feel free to submit a pull request!

@dev-jonghoonpark
Copy link
Contributor

dev-jonghoonpark commented May 2, 2024

hello. @TimvdLippe

I found a problem and fixed the code.

I think the problem was that we used stack inside ArgumentMatcherStorageImpl.java, so the order was reversed during the push/pop process.

so I changed code like this (I modified it to assign the result of the popMatcher to m2 first)

public void reportAnd() {
    assertStateFor("And(?)", TWO_SUB_MATCHERS);

    ArgumentMatcher<?> m2 = popMatcher();
    ArgumentMatcher<?> m1 = popMatcher();

    reportMatcher(new And(m1, m2));
}

@Override
public void reportOr() {
    assertStateFor("Or(?)", TWO_SUB_MATCHERS);

    ArgumentMatcher<?> m2 = popMatcher();
    ArgumentMatcher<?> m1 = popMatcher();

    reportMatcher(new Or(m1, m2));
}

Before the fix, m1 and m2 were reversed, as follows

before change

After the fix, m1 and m2 were assigned as expected, as follows

after change

I was happy to think I had fixed the bug.
But sadly, the error changed to the following
(I used the example code provided by the author)

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.example.demo.MockitoTest.test(MockitoTest.java:22)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
	at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:192)
	...

I think it's correct that I'm getting an UnnecessaryStubbingException because I didn't enter the expected input. (Passed "hello", not any(MyPojo.class))
However, something feels like the result is not clear.

What are your thoughts?
I'm wondering if this is a good fix.

If this is the right fix, I'll raise a PR with the addition of test code for this.

(Note that the ./gradlew check passed without fail.)

Have a nice day

@DidierLoiseau
Copy link
Author

I think it's correct that I'm getting an UnnecessaryStubbingException because I didn't enter the expected input. (Passed "hello", not any(MyPojo.class))
However, something feels like the result is not clear.

I think this is normal, I didn’t write the MRE such that the mock would actually be used, but you could simply add a second call with a parameter that matches the mock, e.g.

myService.doSomething(new MyPojo());

dev-jonghoonpark added a commit to dev-jonghoonpark/mockito-forked that referenced this issue May 3, 2024
dev-jonghoonpark added a commit to dev-jonghoonpark/mockito-forked that referenced this issue May 3, 2024
…ers.or() not to swap the order of matchers
dev-jonghoonpark added a commit to dev-jonghoonpark/mockito-forked that referenced this issue May 9, 2024
dev-jonghoonpark added a commit to dev-jonghoonpark/mockito-forked that referenced this issue May 9, 2024
dev-jonghoonpark added a commit to dev-jonghoonpark/mockito-forked that referenced this issue May 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants