Java

Mockito: Mocking, Stubbing, and Verifying

Executive Summary

Mockito’s @Mock creates a double of a type, an interface or open class. Its methods return defaults and record every call. Then @InjectMocks builds the class under test with those doubles injected. As a result, the test constructs exactly one real object. Stubbing answers with when(mock.method(args)).thenReturn(value). It feeds failures with thenThrow for the exception paths. Meanwhile, argument matchers, eq and any, generalize what a stub responds to. The rule is that a call either uses all matchers or all raw values, never a mixture. Verification is the second half of the contract. First, verify(mock, times(1)).method(args) asserts the interaction happened, while never() asserts it did not. Then ArgumentCaptor inspects what the code actually passed, after the fact.

Mockability is a design property, because Mockito can replace only injected dependencies. That is the constructor-injection argument the dependency injection article makes. In contrast, code that hard-constructs its collaborators resists testing. One discipline keeps suites honest. Mock the unit’s boundary, never the domain’s own logic, and stub only what the test path needs. Also, verify only interactions that are part of the contract. Finally, prefer a small honest fake, such as an in-memory list for a repository, when maintaining one is cheaper than mocking a wide interface. Over-mocked suites verify mocks instead of behavior and pass while integration rots. So every mock must earn its place.

The Problem: The Database Is Not a Unit

The JUnit article’s rule was to test the logic directly, and it works until the logic has collaborators. An OrderService that charges a real PaymentGateway in a test needs credentials, a network, and money. The options, honestly:

Option Speed What it really tests Cost
Real dependency Slow, variable The whole path, including someone else’s system Flaky tests, external failures, setup state
Fake, hand-written in-memory implementation Fast Your logic against a simplified stand-in You maintain the fake honestly
Mock, generated double Fast Your logic and its interactions with the boundary Behavioral coupling, over-verification risk

All three are legitimate, and they map onto the classic test double taxonomy. Mature suites use all three: fakes and mocks for fast unit tests, and the real dependency for a small set of integration tests. The craft is the ratio and the placement. The rest of this article is about doing the mock row correctly with Mockito.

Mocking and Stubbing: The Working Example

The unit: an OrderService that charges through a PaymentGateway interface and decides what to do based on the answer:

public interface PaymentGateway {
    PaymentResult charge(String cardToken, BigDecimal amount);
}

public class OrderService {
    private final PaymentGateway gateway;

    public OrderService(PaymentGateway gateway) {   // injected: mockable by design
        this.gateway = gateway;
    }

    public OrderResult place(Order order) {
        var result = gateway.charge(order.cardToken(), order.total());
        return result.approved() ? OrderResult.CONFIRMED : OrderResult.DECLINED;
    }
}

The test, with mock creation, stubbing, and the assertion, in the JUnit shape you already know:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    PaymentGateway gateway;                  // the double: records every call

    @Test
    void confirmsOrderWhenPaymentIsApproved() {
        when(gateway.charge("tok_123", new BigDecimal("19.99")))
            .thenReturn(PaymentResult.approved());       // the stub: an answer

        var service = new OrderService(gateway);         // inject the double
        var outcome = service.place(new Order("tok_123", new BigDecimal("19.99")));

        assertEquals(OrderResult.CONFIRMED, outcome);
    }
}

Read the anatomy once. @Mock creates the double, and when(…).thenReturn(…) stubs one specific call. Next, the test constructs the service with the double, exactly as production constructs it with the real gateway. Finally, the assertion checks the unit’s own behavior, not the mock’s. @InjectMocks can construct the service automatically when constructor injection makes the wiring obvious. Still, the explicit new OrderService(gateway) you see here is the honest version. It is the same shape the dependency injection article will formalize.

Stubbing the Edges: Failures, Sequences, and Matchers

The failure paths matter more than the happy one, and thenThrow covers them:

// the decline path: a value that says no
when(gateway.charge(anyString(), any(BigDecimal.class)))
    .thenReturn(PaymentResult.declined());

// the outage path: the gateway itself fails, the exceptions article's territory
when(gateway.charge(anyString(), any(BigDecimal.class)))
    .thenThrow(new GatewayTimeoutException("upstream timed out"));

// consecutive answers: first call declines, second succeeds
when(gateway.charge(anyString(), any()))
    .thenReturn(PaymentResult.declined())
    .thenReturn(PaymentResult.approved());

The anyString() and any(…) arguments are argument matchers. They obey one strict rule: once one argument uses a matcher, every argument must. when(gateway.charge(eq(“tok_123”), any())) is legal, but gateway.charge(“tok_123”, any()) is a Mockito runtime error. In fact, it is the single most common Mockito question on the internet. The deeper guidance is restraint. Matchers widen what a stub responds to. Thus, a suite full of any() stubs is a suite whose tests no longer say what they tested.

Verify: Asserting the Interaction Itself

Sometimes the interaction is the behavior: sending exactly one email, charging exactly once, never retrying a decline. Mockito records every call on the mock, and verify asserts against that record:

import static org.mockito.Mockito.*;

// did the service charge exactly once?
verify(gateway, times(1)).charge(anyString(), any());

// did the failure path NOT charge? the contract that matters in refunds
verify(gateway, never()).charge(anyString(), any());

// what exactly did we charge? capture the argument after the fact
var captor = ArgumentCaptor.forClass(BigDecimal.class);
verify(gateway).charge(eq("tok_123"), captor.capture());
assertEquals(new BigDecimal("19.99"), captor.getValue());   // the real amount, as passed

ArgumentCaptor deserves the emphasis because it answers the question matchers cannot. It checks not “some amount was charged” but “this exact amount was charged”. It checks after the call, with full JUnit assertions. The companion warning deserves equal emphasis. Every verify line couples your test to the interaction pattern. So a suite that verifies every call is a suite that fails on every refactor. The working rule from the good tests article is simple. Verify the interactions that are part of the contract, and let return-value assertions carry the rest.

Mockability Is a Design Property

Every example so far mocked an interface that arrived through the constructor, and that was not a convenience choice. You can only replace a dependency your code does not hard-construct. So mockability is an architectural fact, not a testing trick:

// WRONG: the collaborator is hard-constructed: no seam, no test double fits
class OrderService {
    private final PaymentGateway gateway = new StripeGateway();   // welded
}

// RIGHT: the collaborator arrives: production passes the real one,
// tests pass a double, and the seam is the constructor
class OrderService {
    private final PaymentGateway gateway;
    OrderService(PaymentGateway gateway) { this.gateway = gateway; }
}

The delta is the difference between code that accepts a dependency injection design and code that resists testing. The same reasoning indicts static utility calls and final classes in hot logic. They are legal Java and hostile seams, so the Mockito documentation keeps them on the edge of what the tool can double. Sometimes you cannot test a class without mocking its private internals. Then the test is not the problem. Instead, the design is asking for a boundary.

How Real Systems Do This

Every mature Java codebase runs Mockito, and the healthy ones converge on the same placement. They put mocks at the ports: the gateways, repositories, and clients the unit talks through. They keep real logic and real value objects inside the test. Finally, they add a thin band of integration tests where the real dependency runs. The unhealthy pattern is equally standardized: mocks inside mocks and verification of internal call chains. Such suites pass green, while the first real integration reveals the parts never actually worked together.

My over-mocking story is the standard version of a universal mistake. A checkout service had 300 unit tests, all green. Every one of them mocked the repository, the pricing client, and the inventory client. The suite ran in four seconds, and everyone was proud of it. Then the first production deployment failed at the first order. The service’s SQL targeted a mocked repository’s imagined interface, and it had a malformed query nobody had ever actually executed.

The unit tests verified that the service talked to the mocks as the mocks expected, which is circular. The missing piece was a handful of integration tests against a real database. The rework was a ratio, not a rewrite. We kept the fast mock tests for branching logic and added five real-database tests for the persistence paths. We also adopted a rule I have kept since: mock the ports, test the domain, and let at least one test suite exercise the real seams.

Decision Framework

  1. Is the dependency at the unit’s boundary, a gateway, client, or repository, slow or external? Mock it, and stub only what the test path needs.
  2. Does the behavior depend on the interaction itself, exactly-once sends, no-retry contracts? Verify with times, never, and ArgumentCaptor for the payload.
  3. Is the dependency a small, well-understood contract like a repository? An honest in-memory fake often reads better than mocks and stays useful across many tests.
  4. Is the collaborator a value object, record, or domain type? Never mock it: construct the real one, because doubles of data are noise.
  5. Is the thing under test itself being mocked? Stop, because the test verifies your mocks, not your code, and is theater.
  6. Can the dependency not be injected at all? Redesign for constructor injection before fighting the tool, per the dependency injection article.
  7. Is the whole test a chain of verifies on internal calls? Rewrite it around observable results, and keep only the contract interactions.

When NOT to Use This

  • Do not mock value types or your own domain objects. Real records are one line, and doubles of them hide type errors the real constructor would catch.
  • Do not verify interactions that are not part of the contract. Every extra verify couples the test to the implementation and fires on harmless refactors.
  • Do not mock the class under test or spy into its internals. If the seams feel necessary, the design wants a boundary, not a spy.
  • Do not use any() everywhere: a stub that answers everything says nothing, and the test no longer specifies its input.
  • Do not let a mock return null silently. The NPE surfaces three frames away in your code. Instead, thenReturn with an honest default or an explicit stub reads better.
  • Do not mock statics and finals reflexively. Redesigning toward injectable seams is cheaper than maintaining a suite wedged against the tool’s limits.

Common Mistakes

  • Mixing raw arguments and matchers in one call: the rule is all matchers or all values. Otherwise, the violation throws at runtime with a message nobody reads twice.
  • Over-verification: asserting every internal call makes refactors fail tests. Then the team’s response, deleting the tests, is worse than the coupling.
  • Stubbing in @BeforeEach for everything: unused stubs are the default MockitoExtension strictness complaint. The complaint is correct, so stub per test what that test needs.
  • Mocking concrete classes with real state: partial mocks and spies are specialist tools. Indeed, a mock of a half-real class is two bugs wearing a test.
  • Chasing interaction patterns instead of results: the strongest tests assert outputs, and verify is for the outputs that are side effects.
  • reset() between tests to share mocks: fresh @Mock per test is the JUnit lifecycle’s whole point. So reset is the shared-state smell again.
  • Trusting a green all-mock suite: at least one test tier must exercise the real seams. Otherwise, the integration bugs wait for production to find them.

Key Takeaways

  • @Mock creates a double that records calls, and constructor injection is the seam that lets the double in. Thus, mockability is a design property.
  • Stubbing answers questions: when(…).thenReturn feeds values, thenThrow feeds the exception paths, and consecutive returns model sequences.
  • Argument matchers generalize a stub: all matchers or all raw values, never a mixture. Also, any() everywhere empties a test of meaning.
  • Verify asserts interactions: times and never for the contract calls, ArgumentCaptor for the exact payloads.
  • Verify only what is contract, and let return-value assertions carry the rest: over-verification is how suites become refactoring friction.
  • Never mock the unit under test or the domain’s own value objects. Also, prefer a small honest fake when the contract is simple.
  • The suite needs a tier that exercises real seams. Mocks make fast logic tests, while a handful of integration tests keep the circular green at bay.

FAQ

What is Mockito in Java?

Mockito is the standard mocking library for Java tests. It creates doubles of interfaces and open classes and stubs their method answers. It also records every interaction and verifies the calls the code under test should have made. It runs as a JUnit 5 extension and keeps unit tests fast by isolating the unit from its dependencies.

What are @Mock and @InjectMocks in Mockito?

@Mock marks a field as a generated double whose methods return defaults and record calls. @InjectMocks constructs the class under test and injects the available mocks into its constructor, setter, or field. The explicit alternative, constructing the object yourself with the mock, makes the wiring visible in the test.

What is an argument matcher in Mockito?

A condition that generalizes what a stub or verify responds to. For example, eq matches an exact value, any matches anything of the type, and anyString matches strings. The rule is that a call uses either all matchers or all raw arguments, never a mixture. Also, matchers should narrow honestly rather than any() everything.

What does verify do in Mockito?

It asserts against the mock’s call record. For example, verify(mock, times(1)).method(…) confirms the interaction happened exactly once, while never() confirms it did not happen. Also, an ArgumentCaptor captures the actual arguments for full assertions after the call. Verify is for interactions that are part of the contract, not for checking implementation details.

What is the difference between a mock and a stub?

A stub feeds prepared answers so the code under test can run: its job is the return value. A mock additionally records interactions so the test can verify the calls: its job includes the behavior at the boundary. Mockito’s objects do both, and the test’s discipline decides which role each one plays.

Conclusion

With Mockito, you can now test logic that depends on the world without the world in the room. You have doubles at the boundary, stubs for the answers, and verification for the contract interactions. You also have the design habit, constructor injection, that makes it all possible. Equally, you now know the failure mode of the tool: the all-mock suite that verifies itself. You also know the ratio that prevents it.

The next article zooms out from individual tests to the suite as a product. It covers test naming and organization, fixtures, builders for hard constructors, and the pyramid’s ratios. It also covers the anti-patterns that make suites slow, flaky, and eventually ignored: shared state, sleep-based waits, and I/O in unit tests.

Mock the ports, test the domain, verify the contract. The rest is restraint.

Last updated on 14 September 2026.

Share this article

Leave a Reply

Your email address will not be published. Required fields are marked *