Skip to content
Chapter 19 · JUnit 5 & Mockito

Verification ​

Assertions (the assertions chapter) check return values: did the system produce the expected result? How it got there does not matter.

java
int sum = calculator.add(5, 5);
assertEquals(10, sum);

Verification checks behavior: which methods of the dependencies were called, how many times, with which arguments, and in what order. It validates the internal flow of a method, not just its output. It is essential when a method returns nothing (void) and its whole effect is a call on a dependency.

Cheat sheet ​

GroupCodePasses when
Basicverify(mock).method()called exactly once (the default)
verify(mock, times(n)).method()called exactly n times
verify(mock, never()).method()never called
Countverify(mock, atLeastOnce()).method()called ≥ 1 time
verify(mock, atLeast(n)).method()called ≥ n times
verify(mock, atMostOnce()).method()called ≤ 1 time
verify(mock, atMost(n)).method()called ≤ n times
verify(mock, only()).method()called once, and nothing else called on the mock
Argumentsverify(mock).method(any())any argument
verify(mock).method(eq(value))exactly this value
verify(mock).method(isNull())a null argument
verify(mock).method(argThat(...))the argument meets a condition
verify(mock).method(captor.capture())always; captures the argument for later checks
OrderInOrdercalls happened in this sequence, across one or more mocks
No callsverifyNoInteractions(mock)the mock was never used
verifyNoMoreInteractions(mock)no calls besides those already verified
Asyncverify(mock, timeout(ms)).method()the call happens within ms; returns as soon as it does
verify(mock, after(ms)).method()waits the full ms, then checks

1. verify(mock).method() ​

java
public class UserService {

    private final UserRepository repository;

    public UserService(UserRepository repository) {
        this.repository = repository;
    }

    public void register(User user) {
        repository.save(user);
    }
}
java
@Test
void testSimpleVerify() {
    // Arrange
    User user = new User(1L, "John");
    UserRepository repository = mock(UserRepository.class);
    UserService service = new UserService(repository);

    // Act
    service.register(user);

    // Assert: save() was called with this user
    verify(repository).save(user);
}

verify(repository).save(user) is the same as verify(repository, times(1)).save(user): called exactly once. Two calls would fail with "Wanted 1 time ... But was 2 times".

verify accepts only mocks and spies. As the architecture chapter showed, every call on a mock or spy is intercepted and recorded in its invocation container (method, arguments, count), and verify reads that record. A real object records nothing, so verify(new Calculator()) fails: "Argument passed to verify() is of type Calculator and is not a mock!"

2. times(n) and never() ​

java
Calculator calc = mock(Calculator.class);
calc.add(1, 2);
calc.add(1, 2);
calc.add(1, 2);

verify(calc, times(3)).add(1, 2);       // passes: exactly 3 times
verify(calc, never()).multiply(1, 2);   // passes: never called

3. atLeastOnce, atLeast, atMostOnce, atMost, only ​

java
Calculator calc = mock(Calculator.class);
calc.add(1, 2);
calc.add(3, 4);
calc.add(5, 6);

verify(calc, atLeastOnce()).add(anyInt(), anyInt());   // 3 >= 1
verify(calc, atLeast(2)).add(anyInt(), anyInt());      // 3 >= 2
verify(calc, atMost(5)).add(anyInt(), anyInt());       // 3 <= 5
java
Calculator calc = mock(Calculator.class);
calc.add(1, 2);

verify(calc, atMostOnce()).add(anyInt(), anyInt());    // 1 <= 1
verify(calc, only()).add(1, 2);                        // add(1, 2) once, nothing else

only() fails if the mock had any other call: another method, or the same method a second time.

4. Argument matching ​

The matchers from the stubbing chapter work in verify too, with the same rule: if one argument uses a matcher, all must.

java
calc.add(5, 10);

verify(calc).add(anyInt(), anyInt());   // values do not matter
verify(calc).add(eq(5), eq(10));        // exactly 5 and 10 (same as add(5, 10))

Custom conditions: argThat, intThat, ... ​

To check a condition on an argument, pass a lambda:

java
calc.add(5, 10);

verify(calc).add(
        intThat(a -> a > 3),   // first argument: any int greater than 3
        eq(10)                 // second argument: exactly 10
);

Use argThat for object arguments (argThat(user -> user.getAge() >= 18)), and the primitive versions intThat, longThat, doubleThat, ... for primitives. (argThat returns null after registering the matcher; passing null to an int parameter would throw a NullPointerException, so primitive parameters need the versions that return 0.)

ArgumentCaptor: capture, then check ​

Each argThat sees only its own argument. A condition across arguments, such as a + b > 50, cannot be written as a matcher (there is no argThat((a, b) -> ...)). An ArgumentCaptor captures the actual arguments so you can check them afterwards with ordinary assertions:

java
calc.add(5, 10);

ArgumentCaptor<Integer> aCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> bCaptor = ArgumentCaptor.forClass(Integer.class);

verify(calc).add(aCaptor.capture(), bCaptor.capture());

assertTrue(aCaptor.getValue() + bCaptor.getValue() > 50);   // fails here: 5 + 10 = 15
  • One captor per argument, of that argument's type. ArgumentCaptor<User> gives a User from getValue().
  • capture() is a matcher, so the matcher rule applies: add(aCaptor.capture(), 10) fails; write eq(10).
  • If the method was called several times, getValue() returns the last value and getAllValues() all of them, in order:
java
calc.add(5, 10);
calc.add(30, 40);

verify(calc, times(2)).add(aCaptor.capture(), bCaptor.capture());
aCaptor.getAllValues();   // [5, 30]
aCaptor.getValue();       // 30

Captors are most useful when the code under test builds an object and passes it to a dependency: capture it, then assert on its fields. With MockitoExtension, a field @Captor ArgumentCaptor<User> userCaptor; is created for you.

5. Order: InOrder ​

When the sequence matters (first charge the customer, then save the order, then send the notification), use InOrder:

java
Calculator calc = mock(Calculator.class);
calc.add(1, 2);
calc.multiply(3, 4);
calc.add(5, 6);

InOrder inOrder = Mockito.inOrder(calc);

inOrder.verify(calc).add(1, 2);        // first
inOrder.verify(calc).multiply(3, 4);   // then
inOrder.verify(calc).add(5, 6);        // then

Across several mocks, pass them all to inOrder:

java
PaymentGateway gateway = mock(PaymentGateway.class);
OrderRepository orderRepo = mock(OrderRepository.class);
NotificationService notifier = mock(NotificationService.class);

// the workflow under test: charge -> save -> notify
gateway.charge(100);
orderRepo.save();
notifier.sendMail("order@test.com");

InOrder inOrder = Mockito.inOrder(gateway, orderRepo, notifier);

inOrder.verify(gateway).charge(100);                   // the customer is charged first
inOrder.verify(orderRepo).save();                      // then the order is saved
inOrder.verify(notifier).sendMail("order@test.com");   // then the mail is sent

How InOrder works: each inOrder.verify must match a call that happened after the call matched by the previous one. It does not require verifying every call, nor that the first verified call was the first call made:

java
// calls were: add(1, 2), multiply(3, 4), add(5, 6)

inOrder.verify(calc).add(1, 2);
inOrder.verify(calc).add(5, 6);        // passes: comes after add(1, 2); multiply is skipped

inOrder.verify(calc).add(5, 6);        // passes on its own
java
inOrder.verify(calc).add(5, 6);
inOrder.verify(calc).add(1, 2);        // FAILS: add(1, 2) was not called after add(5, 6)
text
Verification in order failure
Wanted but not invoked:
calculator.add(1, 2);
Wanted anywhere AFTER following interaction:
calculator.add(5, 6);

6. verifyNoInteractions and verifyNoMoreInteractions ​

verifyNoInteractions(mock): the mock was never used at all.

java
UserRepository repository = mock(UserRepository.class);
NotificationService notifier = mock(NotificationService.class);

repository.findById(1L);              // only the repository is used

verifyNoInteractions(notifier);       // passes
verifyNoInteractions(repository);     // would fail

verifyNoMoreInteractions(mock): after the calls you have verified, there were no other calls. It does not mean "no calls at all".

java
Calculator calc = mock(Calculator.class);
calc.add(1, 2);
calc.multiply(3, 4);

verify(calc).add(1, 2);
verify(calc).multiply(3, 4);

verifyNoMoreInteractions(calc);       // passes: both calls were verified

If the code also called calc.multiply(5, 6), the last line would fail with "No interactions wanted here", listing the unverified call. Use it sparingly: it makes tests fail on any harmless extra call.

7. Asynchronous code: timeout() and after() ​

java
public class AsyncOrderService {

    private final OrderRepository orderRepository;
    private final NotificationService notificationService;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    public AsyncOrderService(OrderRepository orderRepository,
                             NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.notificationService = notificationService;
    }

    public boolean placeOrder(String customerEmail) {
        // 1. save the order, synchronously
        orderRepository.save();

        // 2. send the email in the background
        executor.submit(() -> {
            try {
                Thread.sleep(100);    // simulate network delay
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            notificationService.sendMail(customerEmail);
        });

        // 3. return without waiting for the email
        return true;
    }
}
java
@Test
void testAsyncOrderService() {
    OrderRepository orderRepo = mock(OrderRepository.class);
    NotificationService emailService = mock(NotificationService.class);
    AsyncOrderService service = new AsyncOrderService(orderRepo, emailService);

    service.placeOrder("customer@example.com");

    verify(orderRepo).save();                                 // passes: it ran synchronously

    verify(emailService).sendMail("customer@example.com");    // FAILS: "Wanted but not invoked"
}

placeOrder returns at once and the test verifies within milliseconds, while the background thread is still sleeping; sendMail has not happened yet. A plain verify checks immediately.

timeout(ms) waits up to ms, checking repeatedly, and returns as soon as the verification passes:

java
verify(emailService, timeout(500)).sendMail("customer@example.com");
// passes after about 100 ms; fails only if 500 ms pass without the call

after(ms) always waits the full ms, then verifies. That makes exact counts reliable. timeout(500) passes as soon as the first call arrives, so it cannot catch a duplicate email that is sent later. after sees every call made in the window:

java
verify(emailService, after(500).times(1)).sendMail("customer@example.com");
// waits 500 ms; fails with "Wanted 1 time ... But was 2 times" if a second mail was sent

after(ms).never() checks that something does not happen within a period. Both modes combine with the counts: timeout(500).times(2), after(500).atMost(1).

Choose timeout for "this must happen" (fast), after for "exactly this many times" or "never" (slower: always waits).

Where to go next ​

This completes unit testing with JUnit 5 and Mockito: each class tested in isolation, with its dependencies mocked. The next level is integration testing, for example with Spring's test support: real beans are created and wired together (a real controller calls a real service, which calls a real repository), and only the edges, such as an external API, are mocked or replaced.

Next: That completes the series. You can now write unit tests with JUnit 5, organise and select them, and isolate each class from its dependencies with Mockito, checking both what it returns and how it uses its collaborators.

Check yourself ​

  1. How does verification differ from an assertion?
  2. What does verify(mock).method() check by default?
  3. Why can verify only be used with mocks and spies?
  4. When does verify(calc, only()).add(1, 2) fail?
  5. Why use intThat instead of argThat for an int parameter?
  6. How do you check a condition involving two arguments together?
  7. With two calls, what do a captor's getValue() and getAllValues() return?
  8. Calls were add(1, 2), multiply(3, 4), add(5, 6). Does inOrder.verify(calc).add(1, 2); inOrder.verify(calc).add(5, 6); pass? And verifying add(5, 6) then add(1, 2)?
  9. What is the difference between verifyNoInteractions and verifyNoMoreInteractions?
  10. Why does a plain verify fail for a call made on a background thread, and how do timeout and after differ?
Answers
  1. An assertion checks the output; verification checks behavior: which dependency methods were called, how often, with which arguments and in which order.
  2. That the method was called exactly once with those arguments: times(1).
  3. Only mocks and spies record their calls (in the invocation container), and verify reads that record; a real object records nothing.
  4. If add(1, 2) was not called exactly once, or if any other call was made on the mock.
  5. argThat returns null, which cannot be passed to an int parameter (NullPointerException); intThat returns 0.
  6. Capture the arguments with ArgumentCaptors in verify, then assert on the captured values.
  7. getValue() returns the last value; getAllValues() returns all of them in call order.
  8. Yes: each verified call comes after the previous one, and skipped calls do not matter. No: add(1, 2) did not happen after add(5, 6).
  9. verifyNoInteractions: the mock was never used. verifyNoMoreInteractions: no calls other than those already verified.
  10. The test verifies before the background thread makes the call. timeout(ms) polls and returns as soon as the verification passes; after(ms) always waits the full time, so it can check exact counts or never().