232

Can anyone please provide me an example showing how to use the 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 don't illustrate it clearly, none of them can explain it with clarity.

1

8 Answers 8

297

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); 
Sign up to request clarification or add additional context in comments.

7 Comments

If doSomething(data) mutates innerData, then will that change be present in assertEquals("Some inner data", actual.innerData), or will innerData be captured as-is before doSomething is executed?
@CoryKlein The OtherClass is a mock, and as it is defined now the doSomething() will not actualy do anything, it simply records the object that was passed. This means that it will be captured as-is before doSomething is executed.
In verify, times(1) is the default and can be omitted.
how does ArgumentCaptor know that foo(other) happened since it is instantiated only after the foo(other) call?
@AvramPop the one who knows this is the mock object. It contains inside a lot of information about the mock. Inside all of that information it also contains the call history for each method with it's parameters. When you call the verify method, it uses that information to run matches against the verification that you are doing. For every parameter it asks whether it matches the specific call it checks. When ArgumentCaptor is checked, it simply stores the values it was invoked with so when verify ends, it holds all the relevant invocations. It's roughly how it works. Hope it helps
|
53

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 Comments

What if there are multiple parameters passed into the method - all of different types? How do you actually verify that boolean parameter was true, for example.
Can you provide an explanation for your comment: Oh, and please consider using TestNG instead of JUnit.. Why consider it? Why change?
@IgorGanapolsky you just add another ArgumentCaptor. ArgumentCaptor<BigDecimal> arg = ArgumentCaptor.forClass(BigDecimal.class); ArgumentCaptor<String> arg2 = ArgumentCaptor.forClass(String.class); Michael michael = new Michael(); michael.sayHi(j); verify(j).saySomething(arg.capture(), arg2.capture()); System.out.println("value is " + arg.getValue()); System.out.println("string is " + arg2.getValue());
38

The steps in order to make a full check are :

First, prepare the argument captor :

ArgumentCaptor<ArgumentClass> argumentCaptor = ArgumentCaptor.forClass(ArgumentClass.class); 

Second, verify the call to the dependent on component (collaborator of subject under test).

times(1) is the default value, so ne need to add it.

verify(dependentOnComponent, times(1)).method(argumentCaptor.capture()); 

Third, get the argument passed to collaborator using getValue() of the captor

ArgumentClass someArgument = messageCaptor.getValue(); 

Fourth, use someArgument for assertions

Comments

12

I created this example that simulates a very simple service that uses a repository to save a String (no dependency injection, no entities), just to teach ArgumentCaptor quickly.

  • The service receives, converts to uppercase and trim a name, then invoke the repository.
  • The repository "saves" the String.
  • With ArgumentCaptor I want to know which value is passed to the repository and then check if it's trimmed and in uppercase, as expected

3 classes: PersonService, PersonRepository and PersonServiceTest (packages omitted)

public class PersonService { private PersonRepository personRepository; public void setPersonRepository(final PersonRepository personRepository) { this.personRepository = personRepository; } public void savePerson(final String name) { this.personRepository.save(name.toUpperCase().trim()); } } public class PersonRepository { public void save(final String person) { System.out.println(".. saving person .."); } } import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; class PersonServiceTest { @Test void testPersonService() { // Create the repository mock final PersonRepository personRepositoryMock = mock(PersonRepository.class); // Create the service and set the repository mock final PersonService personService = new PersonService(); personService.setPersonRepository(personRepositoryMock); // Save a person personService.savePerson("Mario "); // Prepare an ArgumentCaptor to capture the value passed to repo.saveMethod final ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); // Capture the argument passed in the unique method invocation verify(personRepositoryMock, times(1)).save(captor.capture()); // Check if the captured value is the expected one final String capturedParameter = captor.getValue(); assertEquals("MARIO", capturedParameter); } } 

Comments

3

You might want to use verify() in combination with the ArgumentCaptor to assure execution in the test and the ArgumentCaptor to evaluate the arguments:

ArgumentCaptor<Document> argument = ArgumentCaptor.forClass(Document.class); verify(reader).document(argument.capture()); assertEquals(*expected value here*, argument.getValue()); 

The argument's value is obviously accessible via the argument.getValue() for further manipulation / checking or whatever you wish to do.

Comments

2

Use annotations for this and most things @Spy, @Mock, @Captor, etc.

Here is a sample (using JUnit 5):

public class Blam { public void foo(final OtherClass other) { final SomeData data = new SomeData("Some inner data"); other.doSomething(data); } } @ExtendWith(MockitoExtension.class) public class TestBlam { @Captor private ArgumentCaptor<SomeData> captorSomeData; private Blam classToTest; @Mock private OtherClass mockOtherClass; @BeforeEach void beforeEach() { classToTest = new Blam(); } @Test void foo_allGood_success() { SomeData actualSomeData; classToTest.foo(mockOtherClass); verify(mockOtherClass).doSomething(captorSomeData.capture()); actualSomeData = captorSomeData.getValue(); assert(stuff about actualSomeData); } } 

Comments

2

ArgumentCaptor is quite useful when a new object is being created within the under test method which can't be mocked / controlled by the test.

For e.g. consider below method which creates an instance of employee nad passes to the employeeTable.save

public void saveEmployee(String name, String department) { Employee employee = new Employee(UUID.randomUUID().toString(),name, department); Instant start = Instant.now(); employeeTable.save(employee); LOGGER.info("Saved - {} {} in {} millis", name, department, timeElapsed); } 

The test for this method would not be complete without ArgumentCaptor.

How do we use it?

@Captor ArgumentCaptor<Employee> employeeCaptor; 

Now use this captor in mockito verify method as

verify(employeeTable,times(1)).save(employeeCaptor.capture()); 

and then retrieve the captured value and assert against

var employee = messagesCaptor.getValue(); assertThat(history.getName()).isEqualTo("Sanjay"); assertThat(history.getDept()).isEqualTo("Engineering"); assertThat(history.getId()).isEqualTo("Engineering"); 

Full test here

@Test void saveEmployee(final CapturedOutput output) { employeeService.saveEmployee("Sanjay", "Engineering"); verify(employeeTable,times(1)).save(employeeCaptor.capture()); var employee = messagesCaptor.getValue(); assertThat(history.getName()).isEqualTo("Sanjay"); assertThat(history.getDept()).isEqualTo("Engineering"); assertThat(history.getId()).isEqualTo("Engineering"); assertThat(output.getOut()).contains("Saved - Sanjay Engineering in")); } 

Comments

-2

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) 

4 Comments

Shouldn't it refer to ArgumentCaptor?
yes we are capturing the listener passed to method login() in example login(LoginProvider loginProvider,LoginListener callback)
Where is the captor defined in your answer?
ArgumentCaptor< LoginListener > listenerCaptor = ArgumentCaptor.forClass(LoginListener.class);

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.