Mockito - how to mock/verify a method call which a

2020-05-06 14:21发布

问题:

I have a method (method1) that I'd like to test, which based on parameters provided creates an object and calls another method (method2). So I'm mocking method2, which accepts an object (sampleObj).

public void method1(booleanParam) {
    if(booleanParam){
        List<SampleObj> fooList = new ArrayList<SampleObj>;
        fooList.add(new SampleObj("another param"));
        anotherService.method2(fooList);
    }
    //some other smart logic here
}

And here's my test with same obfuscated names (sorry if I missed any typo):

public void testMethod1() {
    AnotherService mockedAnotherService = PowerMockito.mock(AnotherService.class);
    ServicesFactory.getInstance().setMock(AnotherService.class, mockedAnotherService);

    List<SampleObj> fooList = new ArrayList<SampleObj>;
    fooList.add(new SampleObj("another param"));

    // assert and verify
    service.method1(true);
    Mockito.verify(mockedAnotherService, times(1)).method2(fooList);
}

The problem is, when I try to mock the anotherService, I need to pass an object to method2, so I have to create a new one. But since it's a new object, it's not the same object, which will be passed from inside the method1, hence the test fails with the exception:

Argument(s) are different! Wanted:
anotherService.method2(
    [com.smart.company.SampleObj@19c59e46]
);
-> at <test filename and line # here>
Actual invocation has different arguments:
anotherService.method2(
    [com.smart.company.SampleObj@7d1a12e1]
);
-> at <service filename and line # here>

Any ideas how to accomplish that?

回答1:

You have a few options:

  1. Implement equals and hashCode on SampleObj. Because you didn't wrap fooList in a matcher, Mockito checks with List.equals, which checks equals for corresponding objects in each List. The default behavior of Object.equals is that a.equals(b) iff a == b--that is, objects are equal iff they refer to the same instance--but you're welcome to override that if every SampleObj("foobar") equals every other SampleObj("foobar").

  2. Use a Hamcrest Matcher you write.

    private static Matcher<List<SampleObj>> isAListWithObjs(String... strings) {
      return new AbstractMatcher<List<SampleObj>>() {
        @Override public boolean matches(Object object) {
          // return true if object is a list of SampleObj corresponding to strings
        }
      };
    }
    
    // in your test
    verify(mockedAnotherService).method2(argThat(isAnObjListWith("another param")));
    

    Note that you could also just make a Matcher of a single SampleObj, and then use a Hamcrest wrapper like hasItem. See more matchers here.

  3. Use a Captor to check equals your own way:

    public class YourTest {
      // Populated with MockitoAnnotations.initMocks(this).
      // You can also use ArgumentCaptor.forClass(...), but with generics trouble.
      @Captor ArgumentCaptor<List<SampleObj>> sampleObjListCaptor;
    
      @Test public void testMethod1() {
        // ...
        verify(mockedAnotherService).method2(sampleObjListCaptor.capture());
        List<SampleObj> sampleObjList = sampleObjListCaptor.getValue();
    
        assertEquals(1, sampleObjList.size());
        assertEquals("another param", sampleObjList.get(0).getTitle());
      }