Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito's Argument Captor Behavior

Tags:

java

mockito

I am facing with issue where I need to track invocation of some method but only with specified argument parameter. See issue below

@Test
public void simpleTest() {
    ArgumentCaptor<Pear> captor = ArgumentCaptor.forClass(Pear.class);
    Action action = mock(Action.class);
    action.perform(new Pear());
    action.perform(new Apple());
    verify(action, times(1)).perform(captor.capture());
}

static class Action {

    public void perform(IFruit fruit) {}

}
static class Pear implements IFruit {}
static class Apple implements IFruit {}

interface IFruit {}

But getting :

org.mockito.exceptions.verification.TooManyActualInvocations: 
action.perform(<Capturing argument>);
Wanted 1 time:
But was 2 times. Undesired invocation:
..

What I am doing wrong? Mockito v 1.10

To be honest it is just for example and my code more complicated and I don't know, how many times perform will be invoked with Apple.class. It doesn't matter for me. I need to verify only call of perform(Pear.class)

UPD: I need to verify that method with Pear.class was called once. Lets imagine that Action is Transaction and perform is save(DomainObject). So I need to be sure that save(MyDomainObject) was called once, but no matter how many Objects were saved before. This action is atomic for Test and I can't reset mock between these operations

like image 576
Bogdan Samondros Avatar asked Jan 19 '18 14:01

Bogdan Samondros


People also ask

What does argument captor do?

Mockito ArgumentCaptor is used to capture arguments for mocked methods. ArgumentCaptor is used with Mockito verify() methods to get the arguments passed when any method is called. This way, we can provide additional JUnit assertions for our tests.

How does a captor work?

It is used to capture argument values for further assertions. We use argument captor with the methods like verify() or then() to get the values passed when a specific method is invoked. It is used to capture the method arguments. It is used to build a new ArgumentCaptor.

How do I initialize an ArgumentCaptor?

The first way to create the argument captor is to use annotation @Captor declared on field. If you are using JUnit 4, you can initialize it with Mockito JUnit Runner. @RunWith(MockitoJUnitRunner. class) public class MockitoArgumentCaptorTest { @Captor private ArgumentCaptor<String> stringCaptor; ... }

What is Mockito any?

Mockito allows us to create mock objects and stub the behavior for our test cases. We usually mock the behavior using when() and thenReturn() on the mock object.


2 Answers

To verify the number of calls with Pear instances parameters, you may use :

verify(action, times(1)).perform(isA(Pear.class));

Cf. Mockito. Verify method param to be a paticular class

Note that since Mockito 2.1, the following would also work :

verify(action, times(1)).perform(any(Pear.class));

cf. public static T any(Class type)

...This is an alias of: isA(Class)...

...Since mockito 2.1.0 any() and anyObject() are not anymore aliases of this method.

like image 92
Arnaud Avatar answered Sep 28 '22 10:09

Arnaud


Workaround using a custom captor class

  @Test
  public void simpleTest() {
    MyArgumentCaptor pearCaptor = new MyArgumentCaptor(Pear.class);
    Action action = mock(Action.class);

    action.perform(new Pear());
    action.perform(new Apple());

    verify(action, times(1)).perform((IFruit) argThat(pearCaptor));

    System.out.println(pearCaptor.getMatchedObj());
  }


  class MyArgumentCaptor extends InstanceOf {
    private Object matchedObj;

    MyArgumentCaptor(Class<?> clazz) {
      super(clazz);
    }

    @Override
    public boolean matches(Object actual) {
      boolean matches = super.matches(actual);
      if (matches) {
        matchedObj = actual;
      }
      return matches;
    }

    Object getMatchedObj() {
      return matchedObj;
    }
  }
like image 21
Marko Vranjkovic Avatar answered Sep 28 '22 12:09

Marko Vranjkovic