Lesson 13 / 25
Verifying Interactions and Capturing Arguments
Verify calls with times, never and inOrder, and capture arguments for assertions.
Checking that the right things happened
When the important outcome of a method is a side effect on a collaborator (an email sent, an event published, a payment requested), verify it. verify(mock).method(args) checks that the call happened exactly once; verify(mock, times(2)), never(), atLeastOnce() and atMost(n) adjust the count. verifyNoMoreInteractions(mock) asserts that nothing else was called, which is strict and can make tests brittle, so use it sparingly. InOrder verifies the order of calls across mocks when sequence matters (charge payment before marking the order paid). ArgumentCaptor captures the actual argument passed so you can make detailed assertions on it, which is clearer than complex argThat matchers; declare it with @Captor or ArgumentCaptor.forClass(...). Verify outcomes that matter to the behaviour, not every internal call: a test that verifies that a repository's findById was called is usually testing implementation details.
Capturing what was sent
The test captures the object passed to a collaborator and asserts on its contents.
Verification, captors and ordering
Assert on the email actually sent and on the order of operations.
@ExtendWith(MockitoExtension.class)
class CheckoutServiceTest {
@Mock PaymentGateway payments;
@Mock OrderRepository orders;
@Mock Mailer mailer;
@Captor ArgumentCaptor<Email> emailCaptor;
@Test
void successfulCheckoutChargesThenConfirmsAndEmails() {
when(payments.charge(any(), anyLong())).thenReturn(PaymentResult.approved("pay_1"));
when(orders.save(any())).thenAnswer(inv -> inv.getArgument(0));
var service = new CheckoutService(payments, orders, mailer);
service.checkout(cart("asha@example.com", 49_900));
InOrder inOrder = inOrder(payments, orders);
inOrder.verify(payments).charge(any(), eq(49_900L));
inOrder.verify(orders).save(argThat(o -> o.status() == OrderStatus.PAID));
verify(mailer).send(emailCaptor.capture());
Email sent = emailCaptor.getValue();
assertThat(sent.to()).isEqualTo("asha@example.com");
assertThat(sent.subject()).contains("Order confirmed");
}
@Test
void declinedPaymentSendsNoEmail() {
when(payments.charge(any(), anyLong())).thenReturn(PaymentResult.declined("insufficient funds"));
var service = new CheckoutService(payments, orders, mailer);
assertThatThrownBy(() -> service.checkout(cart("asha@example.com", 49_900)))
.isInstanceOf(PaymentDeclinedException.class);
verify(mailer, never()).send(any());
}
}Stub queries, verify commands
A useful rule: stub methods that return data (queries) and verify methods that change things (commands). Verifying a query that was only stubbed adds brittleness without testing behaviour.
Quick check: Which Mockito feature lets you inspect the actual object passed to a mocked method?
- ArgumentCaptor
- InOrder
- thenAnswer
- doNothing
Answer
ArgumentCaptor — ArgumentCaptor captures arguments so the test can assert on their contents.