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

Overloaded method issues with strict #1496

Open
DavidTanner opened this issue Sep 20, 2018 · 13 comments
Open

Overloaded method issues with strict #1496

DavidTanner opened this issue Sep 20, 2018 · 13 comments

Comments

@DavidTanner
Copy link

When adding mocks for overloaded methods using Strict, Mockito throws an error that the wrong method was mocked.

mockito 2.22.0, JDK 1.8.0_181

package test;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class MockitoTest {

    @Test
    void validateStrictness() {
        MyInterface myInterface = mock(MyInterface.class);
        String expected1 = "oneArgument";
        String expected2 = "twoArguments";
        when(myInterface.getString(any())).thenReturn(expected1);
        when(myInterface.getString(any(), any())).thenReturn(expected2);

        String actual = myInterface.getString("one");
        assertEquals(actual, expected1);
        actual = myInterface.getString("one", "two");
        assertEquals(actual, expected2);
    }

    public interface MyInterface {
        String getString(String one);

        String getString(String one, String two);
    }
}
org.mockito.exceptions.misusing.PotentialStubbingProblem: 
Strict stubbing argument mismatch. Please check:
 - this invocation of 'getString' method:
    myInterface.getString(null, null);
    -> at test.MockitoTest.validateStrictness(MockitoTest.java:21)
 - has following stubbing(s) with different arguments:
    1. myInterface.getString(null);
      -> at test.MockitoTest.validateStrictness(MockitoTest.java:20)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
  - stubbing the same method multiple times using 'given().will()' or 'when().then()' API
    Please use 'will().given()' or 'doReturn().when()' API for stubbing.
  - stubbed method is intentionally invoked with different arguments by code under test
    Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.

    at test.MockitoTest.validateStrictness(MockitoTest.java:21)


Process finished with exit code 255
@DavidTanner
Copy link
Author

Looking at the code, the issue appears to be from a naive stub comparison. The parameters of the method aren't compared, just the name.

https://github.com/mockito/mockito/blob/v2.22.0/src/main/java/org/mockito/internal/junit/DefaultStubbingLookupListener.java#L59-L60

@DavidTanner
Copy link
Author

Digging deeper this is being caused because I am trying to mock the overloaded method.

@DavidTanner
Copy link
Author

It looks like this has to do with calling when the second time. I couldn't find what was going on, but the second when doesn't attempt to add the mock logic, it just calls the method. Replacing with doReturn().when() works.

        doReturn(expected1).when(overloadedMethods).getString(any());
        doReturn(expected2).when(overloadedMethods).getString(any(), any());

@philipmw
Copy link

philipmw commented Oct 12, 2018

I ran into this issue today, with the following two stubs:

when(myMock.doSomething("hello")).thenReturn("world");
when(myMock.doSomething("world")).thenReturn("hello");

The documentation is clear that this is okay. But I am getting the "Strict stubbing argument mismatch." error with Mockito 2.22.x.

@ahasbini
Copy link

In reference to @DavidTanner comment #1496 (comment), I have said the same in #1353. I had a look at it and tried to modify the logic of comparison to include the parameters as well, so my final outcome was like this:

private List<Invocation> potentialArgMismatches(Invocation invocation) {
    List<Invocation> matchingStubbings = new LinkedList<Invocation>();
    Collection<Stubbing> stubbings = mockingDetails(invocation.getMock()).getStubbings();
    for (Stubbing s : stubbings) {
        if (!s.wasUsed() && s.getInvocation().getMethod().equals(invocation.getMethod())) {
            matchingStubbings.add(s.getInvocation());
        }
    }
    return matchingStubbings;
}

I've done this about 1 year and half ago with mockito release 2.16.0. If I recall correctly I had some tests setup to check that PotentialStubbingProblem would be thrown in valid cases. Here's the complete test done with AndroidJUnit4 runner if anyone is interested (sorry for tying it to an Android, I was fond of them only back then): MockitoOverloadInstrumentationTest.java

The documentation of Method.equals as per the Java Docs:

public boolean equals(Object obj)
Compares this Method against the specified object. Returns true if the objects are the same. Two Methods are the same if they were declared by the same class and have the same name and formal parameter types and return type.

@FWinkler79
Copy link

Same problem here. This is quite a nuisance. Since this has been open for quite a while already: are there any plans to change that?

@TimvdLippe
Copy link
Contributor

This should have been fixed in #1539 but was not automatically closed. Are you still able to reproduce this issue on Mockito 3.3.9?

@FWinkler79
Copy link

Hi! I am using Mockito-Junit-Jupiter 3.3.3, and made sure that it also uses Mockito-Core 3.3.3 underneath, but the problem is still there.
I am a bit puzzled about you mentioning version 3.3.9 - I could not find that on Maven Central.
Is this already released?

@TimvdLippe
Copy link
Contributor

No 3.3.9 is the latest version that is available on Bintray: https://bintray.com/mockito/maven/mockito-development. But 3.3.3 is recent enough as well.

Please open a PR with a test case added to our test suite that shows the problem and we can take a look. CC @mockitoguy

@FWinkler79
Copy link

Just noticed version 3.3.9 on bintray is mockito-android.
I am not using the android version, but using it in combination with Spring Boot on desktop machine. Not sure that makes any difference...

@FWinkler79
Copy link

I have created a very simple project setup that reproduces the issues:
https://github.com/FWinkler79/Mockito-Issues

Contains 5 Tests, out of which 3 are failing.
The thing seems to be a problem when overloading functions / methods and calling them with null-parameters.

Would be great if you could have a look at this. Maybe I am doing it wrong?

@TimvdLippe
Copy link
Contributor

Which ones are failing and which ones aren't? Please annotate the tests if they are failing and on which line.

Reading the tests, I would expect the following:
Test1: should fail, as you are not stubbing the method with Duration, but you are calling that particular method in TokenProvider
Test2: should fail, as any(Duration.class) matches non-null Duration. You are passing null, so that doesn't match
Test3: should pass
Test4: should fail, for the same reason of Test2
Test5: should pass

@FWinkler79
Copy link

FWinkler79 commented Apr 29, 2020

Sorry, my bad,
the concept of Null-Argument matchers was new to me.
I figured out the solution now and described it in the sample above.

So in short the solution is to use the ArgumentMatchers.isNull() method like so:

@ExtendWith(MockitoExtension.class)
public class Test1 {
  
  @Mock
  Token token;
  
  @Mock
  OAuth2TokenClient tokenClient;
  
  @Test
  void testFunctionOverloading() throws MalformedURLException {
    
    when(tokenClient.fetchToken(any(URL.class), anyString(), anyString(), (Duration) ArgumentMatchers.isNull())).thenReturn(token);
    
    TokenProvider provider = new TokenProvider(tokenClient);
    provider.getToken(URI.create("http://test.com").toURL(), "username", "password");
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants