Example on Mockito's argumentCaptor

2019-01-30 13:25发布

问题:

Can anyone please provide me an example showing what is the use of org.mockito.ArgumentCaptor class and how it is different from simple matchers that are provided with mockito.

I read the provided mockito documents but those doesn't illustrate it clearly, neither they are able to explain it with clarity.

回答1:

I agree with what @fge said, more over. Lets look at example. Consider you have a method:

class A {
    public void foo(OtherClass other) {
        SomeData data = new SomeData("Some inner data");
        other.doSomething(data);
    }
}

Now if you want to check the inner data you can use the captor:

// Create a mock of the OtherClass
OtherClass other = mock(OtherClass.class);

// Run the foo method with the mock
new A().foo(other);

// Capture the argument of the doSomething function
ArgumentCaptor<SomeData> captor = ArgumentCaptor.forClass(SomeData.class);
verify(other, times(1)).doSomething(captor.capture());

// Assert the argument
SomeData actual = captor.getValue();
assertEquals("Some inner data", actual.innerData);


回答2:

The two main differences are:

  • when you capture even a single argument, you are able to make much more elaborate tests on this argument, and with more obvious code;
  • an ArgumentCaptor can capture more than once.

To illustrate the latter, say you have:

final ArgumentCaptor<Foo> captor = ArgumentCaptor.forClass(Foo.class);

verify(x, times(4)).someMethod(captor.capture()); // for instance

Then the captor will be able to give you access to all 4 arguments, which you can then perform assertions on separately.

This or any number of arguments in fact, since a VerificationMode is not limited to a fixed number of invocations; in any event, the captor will give you access to all of them, if you wish.

This also has the benefit that such tests are (imho) much easier to write than having to implement your own ArgumentMatchers -- particularly if you combine mockito with assertj.

Oh, and please consider using TestNG instead of JUnit.



回答3:

Consider a scenario where we are testing a method that depends on a collaborator. This collaborator takes an argument while calling one of its methods. Now there can be two scenarios-

1. The argument is passed externally to the method we are testing and then used by the collaborator during its own method call

method(argument arg)
{
    collaborator.callMethod(arg);
}

To test this method we mock the collaborator and then call the method as follows

method(arg1);
Mockito.verify(collaborator).callMethod(arg1);

So here in the test method we have the arg1 instance and hence can be verified

2. The argument being used by the collaborator to make its own method call is not passed externally and but created inside the method being tested

method()
{
  arg = CreateArgumentInternally();
  collaborator.callMethod(arg);
}

To test this method we mock the collaborator and then call the method as follows

method();

But how do we verify the collaborator was called with which arguments since we don't have the access to the argument as it was created internally inside the method. This where the Mockito ArgumentCaptor comes into picture.
Using the ArgumentCaptor we can get the argument instance created internally and used in the collaborator call and thus we can verify it.

Mockito.verify(collaborator).callMethod(captor.capture());
Argument actual = captor.getValue();

Reference- Understanding Mockito ArgumentCaptor using Spring Boot Test Example



回答4:

The steps in order to make a full check are:

Prepare the captor :

ArgumentCaptor<SomeArgumentClass> someArgumentCaptor = ArgumentCaptor.forClass(SomeArgumentClass.class);

verify the call to dependent on component (collaborator of subject under test) times(1), is the default value, so ne need to add it.

verify(dependentOnComponent, times(1)).send(someArgumentCaptor.capture());

Get the argument passed to collaborator

SomeArgumentClass someArgument = messageCaptor.getValue();

someArgument can be used for assertions



回答5:

Here I am giving you a proper example of one callback method . so suppose we have a method like method login() :

 public void login() {
    loginService = new LoginService();
    loginService.login(loginProvider, new LoginListener() {
        @Override
        public void onLoginSuccess() {
            loginService.getresult(true);
        }

        @Override
        public void onLoginFaliure() {
            loginService.getresult(false);

        }
    });
    System.out.print("@@##### get called");
}

I also put all the helper class here to make the example more clear: loginService class

public class LoginService implements Login.getresult{
public void login(LoginProvider loginProvider,LoginListener callback){

    String username  = loginProvider.getUsername();
    String pwd  = loginProvider.getPassword();
    if(username != null && pwd != null){
        callback.onLoginSuccess();
    }else{
        callback.onLoginFaliure();
    }

}

@Override
public void getresult(boolean value) {
    System.out.print("login success"+value);
}}

and we have listener LoginListener as :

interface LoginListener {
void onLoginSuccess();

void onLoginFaliure();

}

now I just wanted to test the method login() of class Login

 @Test
public void loginTest() throws Exception {
    LoginService service = mock(LoginService.class);
    LoginProvider provider = mock(LoginProvider.class);
    whenNew(LoginProvider.class).withNoArguments().thenReturn(provider);
    whenNew(LoginService.class).withNoArguments().thenReturn(service);
    when(provider.getPassword()).thenReturn("pwd");
    when(provider.getUsername()).thenReturn("username");
    login.getLoginDetail("username","password");

    verify(provider).setPassword("password");
    verify(provider).setUsername("username");

    verify(service).login(eq(provider),captor.capture());

    LoginListener listener = captor.getValue();

    listener.onLoginSuccess();

    verify(service).getresult(true);

also dont forget to add annotation above the test class as

@RunWith(PowerMockRunner.class)
@PrepareForTest(Login.class)