---
title: "Excessive Mocking"
type: "test-smell"
slug: "excessive-mocking"
url: "http://localhost:3000/en/test-smells/excessive-mocking.md"
category: "Mocking Smells"
description: "A test wires up so many mock objects and stubbed interactions that the mock setup dwarfs the actual verification, so the test ends up exercising the mocks rather than real behavior."
---
# Excessive Mocking

> A test wires up so many mock objects and stubbed interactions that the mock setup dwarfs the actual verification, so the test ends up exercising the mocks rather than real behavior.

## Signs and Symptoms

A test is mostly _arrange_: long blocks of mock creation, `when(...).thenReturn(...)` / `mockReturnValue(...)` and `verify(...)` lines, with little real code under test. Tell-tale signs:

* **More mock plumbing than assertions.** The setup is many lines; the "act" is one call; the "assert" checks that mocks were _called_ rather than that an outcome is correct.
* **Mocking things you own.** Domain objects, value objects, or pure-logic collaborators are mocked instead of only the external boundaries (network, DB, clock, filesystem, third-party).
* **Mock chains / "train wrecks."** A mock returns another mock that returns another mock, mirroring the production call graph.
* **Interaction-only verification.** The test asserts `toHaveBeenCalledWith(...)` on every collaborator and never asserts the returned value or resulting state.
* **Brittle on refactor.** Reordering internal calls or extracting a method breaks many tests even though behavior is unchanged.
* **Five-plus mocks just to instantiate the SUT** — usually a sign the SUT itself has too many dependencies.

```js
test('places order', () => {
  const inventory = { check: jest.fn().mockReturnValue(true) };
  const pricing   = { quote: jest.fn().mockReturnValue(42) };
  const tax       = { calc:  jest.fn().mockReturnValue(4.2) };
  const wallet    = { charge: jest.fn().mockReturnValue({ ok: true }) };
  const ledger    = { record: jest.fn() };
  const emailer   = { send: jest.fn() };
  const audit     = { log: jest.fn() };
  const clock     = { now: jest.fn().mockReturnValue(0) };

  const svc = new OrderService(inventory, pricing, tax, wallet, ledger, emailer, audit, clock);
  svc.place(cart);

  // asserts the mocks were called — not that the order is right
  expect(inventory.check).toHaveBeenCalled();
  expect(pricing.quote).toHaveBeenCalled();
  expect(wallet.charge).toHaveBeenCalledWith(46.2);
  expect(ledger.record).toHaveBeenCalled();
});

```

## Reasons for the Problem

**Why it happens**

* **The SUT has too many collaborators.** Excessive mocking is usually a _design_ signal: a class with low cohesion and many dependencies forces every test to stand up all of them. As the catalog literature puts it, _"if you need to mock five internal classes just to test one method, the method has too many dependencies — that's a design problem, not a testing problem."_
* **Habit / "mock everything" reflex.** Taking interaction-based ("London school") testing to an extreme, or reflexively mocking to avoid touching a DB or network, leads to mocking pure logic that could be tested directly.
* **Hard-to-build real objects.** When real collaborators are awkward to construct, a mock feels easier than fixing the constructor or adding a fake.

**Why it hurts**

* **False confidence.** Mocks encode _your assumptions_ about a dependency. If the real implementation diverges, the test still passes — e.g. a stub that claims `sum()` returns only positive integers keeps a green test after the real method changes. Such tests can become tautological: they only verify that the mocks you wrote behave like the mocks you wrote. As Mockito's own docs warn, _"if everything is mocked, are we really testing the production code?"_
* **Brittleness / high maintenance.** Verifying specific calls treats implementation details as the contract, so harmless refactors (call order, extracted helpers) break tests. This is Meszaros's _Overspecified Software_ / _Fragile Test_.
* **Poor readability.** Hundreds of lines of dependency configuration bury the one thing the test is about, much like a _Mystery Guest_ — a reviewer can't tell what behavior is actually asserted.
* **Missed integration bugs.** The real wiring between components is never exercised; bugs live precisely in the seams the mocks replaced. High coverage masks low quality.

## Treatment

Treat heavy mocking as feedback, then reduce the _need_ to mock:

1. **Fix the design first.** If you must mock 5+ collaborators to build the SUT, split responsibilities or reduce constructor dependencies. Fewer real dependencies means fewer mocks.
2. **Mock only at architecture boundaries.** Mock the things you _don't own_ (network, DB, clock, filesystem, third-party APIs); use **real instances** of your own domain and value objects.
3. **Extract a pure core (functional core / imperative shell).** Move calculation/decision logic out of the I/O-heavy class so it can be tested with **zero mocks**, leaving a thin shell that needs only one or two boundary doubles.
4. **Prefer state verification over interaction verification.** Assert the returned value or resulting state instead of `verify(...)`/`toHaveBeenCalledWith(...)` on every collaborator. Reserve interaction checks for the one side effect that genuinely matters.
5. **Use the simplest double that works.** Replace elaborate mocks with **stubs** (just return values) or a single reusable **in-memory fake** instead of re-stubbing each method per test.
6. **Surface over-mocking dynamically.** Enabling Mockito strict stubbing (the default `MockitoExtension`/`MockitoJUnitRunner`) throws `UnnecessaryStubbingException` for stubs that are configured but never used — a cheap way to find mocks you didn't need.

```js
// BEFORE: 8 mocks, asserting calls
const pricing = { quote: jest.fn().mockReturnValue(42) };
const tax     = { calc:  jest.fn().mockReturnValue(4.2) };
// + 6 more mocks ...
expect(wallet.charge).toHaveBeenCalledWith(46.2);

// AFTER: pure logic tested directly — no mocks
expect(totalFor(cart, rates)).toBe(46.2);

// only the real boundary is faked; assert state, not calls
const wallet = new InMemoryWallet({ balance: 100 });
const svc = new OrderService(new InMemoryInventory(cart), wallet);
const order = svc.place(cart);
expect(order.total).toBe(46.2);
expect(wallet.balance).toBe(53.8);

```
