Mid-level 3 min · March 30, 2026

Mockito verify() — How Over-Verification Halved Payments

A renamed method caused verify() to pass on a dead mock, halving payments in production.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Mockito verify() asserts that an expected interaction with a mock actually occurred
  • Default mode checks exactly one call; use times(n), atLeastOnce(), never() for other counts
  • Argument matchers (any(), eq(), argThat()) give flexible matching but must be used consistently across all arguments
  • ArgumentCaptor captures generated values (UUIDs, timestamps) for post-hoc assertions
  • Over-verifying internal calls creates brittle tests that break on refactoring – only verify meaningful side effects
Plain-English First

Mockito verify() is how you assert behaviour, not state. When a test checks that account.getBalance() equals 1000, that's asserting state. When a test checks that notificationService.sendEmail() was called exactly once with the right email address, that's asserting behaviour — and verify() is the tool for it.

There are two schools of thought on verify() in unit tests. One says: verify everything — assert that every collaborator method was called as expected. The other says: verify only the meaningful side effects — calling a payment processor, sending a notification — and trust that state assertions cover the rest.

After ten years of writing and reviewing Java tests, I'm firmly in the second camp. Verifying every internal method call ties your tests to the implementation rather than the contract. When you refactor, tests break for reasons unrelated to correctness. Verify interactions that represent side effects your callers care about. Leave implementation details to state assertions.

Basic verify() and Verification Modes

The basic verify(mock).method(args) asserts the method was called exactly once with the given arguments. Verification modes control the count: times(n), atLeastOnce(), atLeast(n), atMost(n), never().

PaymentProcessorTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package io.thecodeforge.payment;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;

class PaymentProcessorTest {

    @Test
    void processPayment_successful_sendsConfirmationEmail() {
        NotificationService notificationService = mock(NotificationService.class);
        AuditService auditService = mock(AuditService.class);
        PaymentProcessor processor =
            new PaymentProcessor(notificationService, auditService);

        PaymentRequest request =
            new PaymentRequest("customer-42", 100_00, "GBP");

        processor.processPayment(request);

        // exactly one confirmation email
        verify(notificationService, times(1))
            .sendConfirmationEmail("customer-42", 100_00);

        // audit logged at least once
        verify(auditService, atLeastOnce())
            .log(eq("PAYMENT_PROCESSED"), anyString());

        // fraud check never called for small amounts
        verify(auditService, never())
            .flagForFraudReview(anyString());
    }

    @Test
    void processPayment_largAmount_triggersFraudCheck() {
        // ... same setup
        processor.processPayment(
            new PaymentRequest("customer-42", 10_000_00, "GBP"));

        verify(auditService, times(1)).flagForFraudReview("customer-42");
    }
}
Output
PaymentProcessorTest > processPayment_successful_sendsConfirmationEmail PASSED
PaymentProcessorTest > processPayment_largAmount_triggersFraudCheck PASSED
The default is times(1)
You don't need to write verify(mock, times(1)).method(). verify(mock).method() is equivalent. Only specify times(n) when n is not 1.
Production Insight
CI pipelines often fail with TooManyActualInvocations when a method is called in a loop with off-by-one errors.
Use atMost(n) for loops where exact count varies, and log the invocation count during debugging.
Rule: Never guess the call count – trace the execution path first.
Key Takeaway
Default verify checks exactly one call.
Choose a mode (times, never, atLeast) based on the contract, not convenience.
Use never() to explicitly assert side effects should not happen.

Argument Matchers in verify()

Exact argument matching works for primitives and objects with correct equals() implementations. For everything else, Mockito's argument matchers give you flexibility: any(), anyString(), eq(), argThat() for custom predicates.

The rule that trips people up: you cannot mix exact values and matchers in the same method call. If one argument uses a matcher, all arguments must use matchers. Wrap exact values in eq().

OrderServiceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.thecodeforge.order;

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
import static org.junit.jupiter.api.Assertions.*;

class OrderServiceTest {

    @Test
    void placeOrder_notifiesWarehouse_withCorrectDetails() {
        WarehouseService warehouse = mock(WarehouseService.class);
        OrderService orderService = new OrderService(warehouse);

        orderService.placeOrder(new Order("order-1", "SKU-42", 3));

        verify(warehouse).dispatchRequest(
            eq("SKU-42"),
            eq(3),
            anyString()
        );

        verify(warehouse).dispatchRequest(
            argThat(sku -> sku.startsWith("SKU-")),
            anyInt(),
            anyString()
        );
    }
}
Output
OrderServiceTest > placeOrder_notifiesWarehouse_withCorrectDetails PASSED
Matchers must be consistent
If you use a matcher like anyString() for one argument, you must use matchers for all arguments of that method call. Wrap literal values with eq(). Forgetting this causes InvalidUseOfMatchersException at runtime.
Production Insight
Teams waste hours debugging InvalidUseOfMatchersException when they add one matcher without wrapping other args.
The exception message is cryptic – it says 'misplaced argument matcher detected' with no line number.
Rule: If you use a matcher, all arguments in that verify call must be matchers – no exceptions.
Key Takeaway
Matchers and exact values cannot mix in the same call.
Wrap exact values with eq() when using any matcher.
argThat() is your escape hatch for complex predicates – but keep them simple and testable.

Using ArgumentCaptor for Generated Values

When the code under test generates a value (an ID, a timestamp, a computed field) and passes it to a collaborator, you can't use eq() because you don't know the value at test-write time. ArgumentCaptor captures the actual argument that was passed, so you can assert on its structure or properties after the fact.

This is especially useful for generated UUIDs, timestamps, or objects with deep fields.

OrderServiceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.thecodeforge.order;

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

class OrderServiceTest {

    @Test
    void placeOrder_capturesGeneratedDispatchRequest() {
        WarehouseService warehouse = mock(WarehouseService.class);
        OrderService orderService = new OrderService(warehouse);

        orderService.placeOrder(new Order("order-1", "SKU-42", 3));

        ArgumentCaptor<DispatchRequest> captor =
            ArgumentCaptor.forClass(DispatchRequest.class);
        verify(warehouse).dispatch(captor.capture());

        DispatchRequest captured = captor.getValue();
        assertEquals("SKU-42", captured.getSku());
        assertEquals(3, captured.getQuantity());
        assertNotNull(captured.getDispatchId());  // generated UUID
        assertTrue(captured.getDispatchedAt().isBefore(Instant.now()));  // current timestamp
    }
}
Output
OrderServiceTest > placeOrder_capturesGeneratedDispatchRequest PASSED
Capture multiple invocations
If the mocked method is called multiple times, captor.getAllValues() returns a List of all captured arguments. Use that to assert on each call's parameters.
Production Insight
A common bug in audit logging is passing the wrong timestamp or missing a generated ID.
ArgumentCaptor catches these because you can assert on the generated field directly.
Rule: If a value is generated inside the SUT, use captor to validate it; don't assume it's correct.
Key Takeaway
Use ArgumentCaptor when you cannot predict the exact argument value.
Capture once with captor.capture() in verify(), then assert with standard assertions.
For multiple calls, use captor.getAllValues() and iterate.

Order Verification with InOrder

Sometimes you need to verify that methods were called in a specific sequence. InOrder verifier lets you enforce call order across one or more mocks.

Use it for scenarios like: user registration must trigger audit log before sending confirmation email. If order doesn't matter, don't use InOrder – it over-specifies.

RegistrationServiceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.thecodeforge.user;

import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import static org.mockito.Mockito.*;

class RegistrationServiceTest {

    @Test
    void registerUser_auditBeforeEmail() {
        AuditService auditService = mock(AuditService.class);
        EmailService emailService = mock(EmailService.class);
        RegistrationService registrationService =
            new RegistrationService(auditService, emailService);

        registrationService.register("user@example.com");

        InOrder inOrder = inOrder(auditService, emailService);
        inOrder.verify(auditService).log("USER_REGISTERED");
        inOrder.verify(emailService).sendWelcomeEmail("user@example.com");
    }
}
Output
RegistrationServiceTest > registerUser_auditBeforeEmail PASSED
When Order Matters
  • Use InOrder when the order of calls is part of the business requirement.
  • Do not use InOrder for calls that happen in separate threads or asynchronous tasks – the order is non-deterministic.
  • InOrder works across multiple mocks; pass them in the order you expect them to be called.
  • InOrder only checks relative order of the verified methods, not that no other calls happened in between.
Production Insight
InOrder verifications often break during asynchronous refactoring when calls shift to different threads.
If you're using CompletableFuture or ExecutorService, the order of mock calls is unpredictable.
Rule: Only use InOrder when the production code is synchronous and ordering is a documented requirement.
Key Takeaway
InOrder enforces call sequence across one or more mocks.
Don't use it for async code – order is non-deterministic.
Overusing InOrder makes tests brittle; only specify order when it's a business invariant.

Best Practices: What to Verify and What Not to Verify

The most common mistake with verify() is overuse. Every verify(mock).method() is a statement that your test knows about internal wiring. That's a liability during refactoring.

Verify only: - Side effects that cross a service boundary (sending email, publishing event, writing to external system). - Calls that represent business transactions (payment, debit, credit). - Calls that are part of a contract with an external system.

Do not verify: - Calls between internal methods of the same class. - Getters or setters (test state instead). - Calls that happen with 100% certainty under the test conditions (if the method under test is simple, trust state assertions).

When in doubt, ask: "If this internal call moves to a different class, should my test still exist?" If yes, verify the boundary. If no, don't verify.

PaymentProcessorTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.thecodeforge.payment;

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

class PaymentProcessorTest {

    @Test
    void processPayment_verifiesOnlyExternalSideEffects() {
        PaymentGateway gateway = mock(PaymentGateway.class);
        NotificationService notification = mock(NotificationService.class);
        PaymentProcessor processor = new PaymentProcessor(gateway, notification);

        PaymentResult result = processor.processPayment(
            new PaymentRequest("customer-42", 100_00, "GBP"));

        // Verify external boundary: payment sent to gateway
        verify(gateway, times(1)).charge(any(PaymentRequest.class));

        // Verify external boundary: confirmation notification
        verify(notification, times(1)).sendConfirmationEmail(eq("customer-42"), anyString());

        // Assert internal state via returned object – no verify needed
        assertEquals(PaymentStatus.SUCCESS, result.getStatus());

        // Do NOT verify internal helpers like validateRequest() or calculateFee()
        // Those are implementation details that can change without affecting correctness.
    }
}
Output
PaymentProcessorTest > processPayment_verifiesOnlyExternalSideEffects PASSED
Boundary vs Implementation
  • Boundary: calls to external APIs, databases, message queues, email, etc.
  • Implementation: calls between private methods, internal calculations, data transformations.
  • If you verify internal calls, you're testing the 'how', not the 'what'. The 'what' is the result and the external side effects.
  • Refactoring internal code should not require changing tests – unless the external contracts change.
Production Insight
Teams that over-verify fear refactoring because every internal rename breaks a test.
This creates a 'test lock-in' where code quality degrades because changes are dangerous.
Rule: A test suite that breaks on internal refactors is not protecting you – it's holding you back.
Key Takeaway
Verify external boundaries, not internal wiring.
State assertions cover the 'what'; verify covers the 'who' and 'how many'.
A good test suite survives refactoring with minimal changes – over-verification is the enemy.
● Production incidentPOST-MORTEMseverity: high

Brittle Tests Masked a Payment Logic Bug in Production

Symptom
All unit tests passed in CI, but customers began seeing incorrect charges. The payment amount was halved after a refactor that renamed an internal method. The test still verified the old method name (which no longer existed) and passed because the mock was never called.
Assumption
The team assumed that as long as verify() calls passed, the payment logic was correct. They had verified every single internal call in the chain, including ones that were later removed.
Root cause
Developers verified every internal call including ones that later moved to a different service. To make tests pass after refactoring, they removed verify calls for moved methods. Those methods contained the actual business logic, and the new service had no corresponding test. A bug in the new service slipped into production.
Fix
Adopt a 'verify only side effects' policy: verify calls that represent external outputs (email, payment, audit). Do not verify internal method calls between collaborators within the same service. Use state assertion on the result to cover internal logic.
Key lesson
  • Verify interactions that cross a service boundary – not internal implementation wiring.
  • Over-verification makes refactoring dangerous: you either break tests or weaken coverage.
  • Use verifyNoMoreInteractions() sparingly – it's often a sign of over-specification.
Production debug guideTrack down misleading test failures caused by incorrect verification4 entries
Symptom · 01
Test fails with WantedButNotInvoked: method was never called
Fix
Check that the method is actually invoked under the test conditions. Add a System.out.println in the production code, or use a breakpoint in the mock method (IDEA allows setting breakpoints on mock calls).
Symptom · 02
Test fails with TooManyActualInvocations: method called more times than expected
Fix
Check if the method is called in a loop or from multiple paths. Use verify(mock, times(n)) with the exact expected count, or use atMost(n) if exact count is not critical. Add logging to count calls.
Symptom · 03
Test fails with ArgumentsAreDifferent: arguments don't match
Fix
Use ArgumentCaptor to capture the actual passed argument and print it. Compare with expected value. Check if equals() is implemented correctly on the argument type, or use argThat() with a predicate.
Symptom · 04
Test fails with InvalidUseOfMatchersException: mixing matchers and exact values
Fix
Wrap all exact values in eq() if at least one matcher is used anywhere in the same method call.
★ Quick Debug Cheat Sheet for Verify FailuresFast fixes for the most common verify() errors in test suites
WantedButNotInvoked
Immediate action
Check if the method under test actually calls the mocked dependency under the given conditions.
Commands
Add a breakpoint in the production class method that should call the mock.
Run the test in debug mode and step through to see if the call happens.
Fix now
If the call never happens, the production code path is wrong – fix the logic or the test conditions.
TooManyActualInvocations+
Immediate action
Reduce expected count or use atMost(n) if exact count is not required.
Commands
Change verify to `atLeastOnce()` to see if the test passes, then examine the calling code for duplicate calls.
Add logging in the real method to count invocations.
Fix now
If the method is called in a loop, ensure the loop condition is correct. If it's called multiple times by design, adjust the expected count.
ArgumentsAreDifferent+
Immediate action
Use ArgumentCaptor to capture and inspect the actual argument.
Commands
Create `ArgumentCaptor<YourType> captor = ArgumentCaptor.forClass(YourType.class);` then `verify(mock).method(captor.capture());`
Print `captor.getValue()` and its fields to see what was actually passed.
Fix now
Adjust your test expectation or fix the production code if it's passing wrong arguments.
Verification Modes Comparison
Verification ModeMeaningExample
times(1) (default)Called exactly onceverify(mock).method()
times(n)Called exactly n timesverify(mock, times(3)).method()
never()Never calledverify(mock, never()).method()
atLeastOnce()Called at least onceverify(mock, atLeastOnce()).method()
atLeast(n)Called at least n timesverify(mock, atLeast(2)).method()
atMost(n)Called at most n timesverify(mock, atMost(5)).method()

Key takeaways

1
verify(mock).method() asserts the method was called exactly once. Add a verification mode (times, never, atLeast) to assert different counts.
2
Argument matchers (any(), anyString(), eq(), argThat()) must be used consistently
if one argument uses a matcher, all must.
3
ArgumentCaptor captures the actual argument passed to a mock method so you can assert on generated or computed values.
4
InOrder verifies call sequence; use it only for synchronous code where order is a business requirement.
5
Verify the meaningful side effects
payment processed, email sent, event published. Don't verify internal implementation details that would break on any refactor.

Common mistakes to avoid

5 patterns
×

Verifying every internal method call

Symptom
Tests break when you rename or merge internal methods during refactoring, discouraging healthy code changes.
Fix
Only verify side effects that cross a service boundary. Test internal logic through state assertions on the return value.
×

Mixing exact values and argument matchers without eq()

Symptom
Mockito throws InvalidUseOfMatchersException at runtime with a confusing error message.
Fix
If any argument uses a matcher (any(), argThat()), wrap all exact values in eq().
×

Calling verify() before the system under test executes

Symptom
Test passes incorrectly because the verify() runs before any interaction happens – Mockito checks past interactions, not future ones.
Fix
Always place verify() calls after the Act step, never before it.
×

Not using ArgumentCaptor for generated values

Symptom
Tests fail with ArgumentsAreDifferent because the expected UUID or timestamp doesn't match what was passed.
Fix
Use ArgumentCaptor to capture the actual generated value and assert on its properties (e.g., non-null, correct type, within time range).
×

Using InOrder for asynchronous code

Symptom
Test passes locally but fails intermittently in CI due to thread scheduling.
Fix
Do not use InOrder when the production code uses threads, executors, or CompletableFuture. Order is non-deterministic.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between Mockito when() and verify()?
Q02JUNIOR
How do you verify a method was called exactly three times in Mockito?
Q03SENIOR
When would you use ArgumentCaptor instead of argument matchers?
Q04SENIOR
Why might over-verifying in tests be an antipattern?
Q05SENIOR
How do you verify that methods were called in a specific order using Moc...
Q01 of 05JUNIOR

What is the difference between Mockito when() and verify()?

ANSWER
when() is used to stub a method – to define what it returns when called. verify() is used to assert that a method was called (and how many times). when() sets up behaviour for the test; verify() checks interactions afterwards. They serve different phases of a test: Arrange (when) vs Assert (verify).
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What does Mockito verify() do?
02
How do I verify a method was never called in Mockito?
03
What is ArgumentCaptor and when should I use it?
04
Can I verify that a method was called with a specific argument without using eq()?
05
Should I use verifyNoMoreInteractions()?
🔥

That's Advanced Java. Mark it forged?

3 min read · try the examples if you haven't

Previous
JUnit 5 Annotations: @Test, @BeforeEach, @AfterEach and More
27 / 28 · Advanced Java
Next
Unit Testing vs Integration Testing: Key Differences