Should I test that?

Should I use mocks in unit tests?

Answer

Yes, use mocks in unit tests for collaborators that leave the process or give a different answer on each run, such as another team's service, an email provider or the clock; use real objects for your own code, in-memory fakes for your own repositories, and assert on results instead of calls.

Verdict on the code under testYes

Why

Yes, use mocks in unit tests at the edges of your own code: for collaborators that leave the process or give a different answer on each run. My typical case is a unit test of a backend service that calls another team's HTTP service, sends email and reads the clock. Blast radius is users and Change frequency is regularly, because customers see the service's output and its rules change with most features. Detectability is eventually, because a wrong call to a collaborator returns a plausible value. Reversibility is with-effort, since records saved with a wrong result need a repair script, and Test cost is moderate, about an hour per behaviour with a double for each collaborator, so rule R11 gives Test.

When the decision changes
WhenDecisionWhy
The unit asks a payment gateway client to charge a customerTest mandatory: mock the gateway client and assert one charge of the exact amount, plus a decline and a timeoutBlast radius rises to money and Reversibility to costly, because a double charge ends in a refund
The unit passes a customer ID to a document storage client and returns the files it gets backTest mandatory: stub exact IDs for two customers and assert that each request carries the caller's own IDBlast radius rises to safety-or-legal and Detectability to never, because another customer's files look like normal output
The unit only forwards one ID to one repository method, and integration tests use data that tells that method apart from its siblingsDo not write a mock-based unit test for the forwarding method; the integration tests cover itDetectability moves to immediately and Reversibility to trivial, because a wrong call fails CI before merge
The failure to catch is a change in another company's API responses that its sandbox does not showTest it differently: alert on production responses that fail to parseTest cost rises to heavy, because a mock repeats the old recording, while Detectability stays eventually and Reversibility with-effort
Only staff use the unit, in a back-office toolTest minimally: one main-path test that mocks only the collaborators that leave the processBlast radius falls to internal and Detectability to same-day, because staff report a wrong result the day they see it

What breaks if you don't test

Without doubles at the edges, the service's unit tests need the partner service running. Those tests fail whenever the partner's staging server is down, or they never get written, and the service's rules first run on real traffic. A rule that picks the wrong user or date returns a plausible result, and a customer reports it weeks later.

What you lose if you over-test

Mocks past the edge fail the other way. A mocked repository returns whatever the test hands it, so the query never runs. Tests that verify() each internal call break when three calls become one batch call, although the result is identical. A stub written with any() stays green when the code passes the wrong ID. Should I mock all dependencies? covers where to stop.

How to test

  1. Mock the collaborators that leave the process or vary between runs: another team's service, an email or payment provider, the clock.
  2. Wrap each third-party SDK in a small interface you own and mock that interface, following the Mockito wiki rule "Don't mock a type you don't own".
  3. Use real objects for your own entities and pure functions, and in-memory fakes for your own repositories, the order that Software Engineering at Google recommends.
  4. Stub with the exact arguments the code should pass, and assert on results. Verify a call only when the call is the behaviour, such as a charge.

When the answer changes

  • The collaborator charges money or returns records that only one customer may see.
  • The unit only forwards a call that integration tests already run.
  • The failure you fear is a change in a partner's responses, which a mock repeats as recorded.

Real incident + Code example

The stub that accepted any user

On a B2B task tracker I worked on, TaskNotifier emailed a task's assignee in the assignee's language, which it read from another team's accounts service. The unit test stubbed accounts.language(any()). A refactor passed task.creatorId() to that lookup instead of task.assigneeId(), and the test stayed green, because the stub answered for any ID. The bug showed only where colleagues used different languages: for five weeks an assignee in Munich got French emails from a colleague in Lyon, until she asked support why. The fixed test stubs the exact ID:

@ExtendWith(MockitoExtension.class) // strict stubs: a call with other arguments fails
class TaskNotifierTest {
    @Mock AccountClient accounts;                 // another team's service: mock it
    InMemoryOutbox outbox = new InMemoryOutbox(); // our own code: a fake we can read

    @Test
    void writesToTheAssigneeInTheAssigneesLanguage() {
        when(accounts.language("u-anna")).thenReturn("de"); // the exact ID, not any()
        var task = new Task("t-1", "Q3 budget", "u-marc", "u-anna"); // creator, assignee

        new TaskNotifier(accounts, outbox).assigned(task);

        Email sent = outbox.only();
        assertEquals("u-anna", sent.recipientId());
        assertEquals("de", sent.language());
    }
}

With the bug, the lookup for u-marc matches no stub, and the strict stubs that MockitoExtension enables by default fail the test.

FAQ

When should I mock?

Mock a collaborator when the real one leaves the process or gives a different answer on each run: another team's service, an email or payment provider, or the clock. Use real objects for your own logic, and an in-memory fake for your own database access.

Should unit tests use mocks?

Unit tests should use mocks only where the real collaborator is remote or gives a different answer on each run. A unit test of a pure calculation needs no mocks, and a unit test that mocks your own classes checks the wiring between them instead of the result.

What is the difference between a mock, a stub and a fake?

A stub returns fixed answers, a fake is a working lightweight implementation such as an in-memory repository, and a mock records calls so the test can verify them. Martin Fowler sets out the distinction in Mocks Aren't Stubs.