skip to content

A Java unit test fails with Mockito's UnnecessaryStubbingException even though every assertion passed. What exactly triggers that exception, when is it reported, and how should you fix it?

level: juniorimportance: must knowfreq 62%

answer

  1. stubbing declared, never matched by a call
  2. reported after the test (per method under JUnit 5 extension)
  3. usual culprit: fat @BeforeEach used by some tests
  4. matcher mismatch can also leave it unused
  5. fix = delete/move; lenient() is the last resort

basics

~20 s

It means a stubbing you declared was never matched by any call during the test. Under strict stubbing Mockito reports it after the test, pointing at the stubbing's line. The fix is usually to delete the stubbing or move it into the test that actually needs it — not to switch strictness off.

solid answer

~60 s

`UnnecessaryStubbingException` says: *you told a mock what to return, and nothing ever asked*. It is raised by strict stubbing (the default for Mockito's JUnit 5 extension) at the end of the test, and the message names the file and line of the unused stubbing. Common causes: - The stubbing is simply dead — leftover from a refactor. - It lives in a shared `@BeforeEach` and only some tests exercise that path; with the JUnit 5 extension the check is per test method, so the others fail. - Production code changed and no longer calls that collaborator — the test is stale and its assertions probably need rethinking, not just a deleted line. - The stubbing's matchers do not match the real call, so a *different* stubbing or the default answer served it. Fixes, in order of preference: delete it; move it into the tests that use it; extract a helper that stubs on demand; and only then relax with `lenient()` on that stubbing or `@Mock(lenient = true)` on that mock.

code

java · 23 lines
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock UserRepository users;
    @InjectMocks OrderService service;

    @BeforeEach
    void setUp() {
        when(users.findById(1L)).thenReturn(new User(1L)); // unused by countAll() -> UnnecessaryStubbingException
    }

    @Test void placesOrder() { service.place(1L, "book"); }
    @Test void countAll()   { service.count(); }
}

// Fix A: move it into the test that needs it
@Test void placesOrder() {
    when(users.findById(1L)).thenReturn(new User(1L));
    service.place(1L, "book");
}

// Fix B: keep the fixture, relax only that mock
@Mock(lenient = true) UserRepository users;

go deeper

for a junior

Say plainly that a stubbing was never called, and that the fix is to delete it or move it into the test that needs it.

for a middle

Add the per-method vs per-class reporting difference and the matcher-mismatch cause, and name the narrow leniency escape hatches.

for a senior

Treat it as a fixture-design signal: distinguish dead stubbings from stale tests where production behaviour changed, and argue against blanket leniency.

for a principal

Frame it as a suite-health signal and discuss how you keep teams fixing causes instead of silencing symptoms as the codebase grows.

## What the exception means A *stubbing* is a recorded instruction on a mock — `when(repo.findById(1L)).thenReturn(user)`. Mockito tracks whether each stubbing was ever *realised*, i.e. matched by an actual invocation on that mock. Under `Strictness.STRICT_STUBS`, any stubbing never realised by the end of the test produces `UnnecessaryStubbingException`, with a message like: ``` Unnecessary stubbings detected. Clean up unnecessary stubbings: 1. -> at com.example.OrderServiceTest.setUp(OrderServiceTest.java:34) ``` The assertions passing is not a contradiction: the test verified what it verified, and separately left an instruction nobody used. ## When it is reported The scope depends on the harness: - **JUnit 5 `MockitoExtension`** — a session per test method, so the check runs after *each* test. A `@BeforeEach` stubbing used by only some tests fails the others. - **JUnit 4 `MockitoJUnitRunner`** (the plain, `Strict` variant) — collects unused stubbings across the whole class and reports once at the end, so a `@Before` stubbing used by any test in the class is accepted. - **`MockitoJUnitRunner.StrictStubs`** / a `MockitoSession` with `STRICT_STUBS` — per-test behaviour like the extension. That scope difference is why a suite can go green on JUnit 4 and light up after a JUnit 5 migration. ## The four causes, and the right fix for each **1. Dead stubbing.** Left behind by a refactor; nothing calls it any more. Delete it. This is the case the exception exists for, and it is the majority. **2. Shared setup used by a subset of tests.** A fat `@BeforeEach` stubs five collaborators so that the three tests that need them are shorter. The real defect is fixture design. Preferred fixes: move each stubbing into the tests that need it; or extract intention-revealing helpers (`givenUserExists(1L)`) that tests call explicitly. If the fixture is genuinely shared and optional, mark that one mock `@Mock(lenient = true)` with a comment. **3. Stale test.** Production code stopped calling the collaborator. Deleting the stubbing makes the build green but leaves a test that no longer asserts what its name claims. Read the production change first, then decide whether the test should assert something new or be deleted along with the behaviour. **4. Matcher mismatch.** You stubbed `findById(1L)` but the code calls `findById(2L)`, or you stubbed `eq(user)` for an object whose `equals` you did not implement. The stubbing is unused because it never matched. Under strict stubbing this usually shows up first as `PotentialStubbingProblem` at the call site; if the method has other stubbings, or the call happened in a path that swallowed the result, you may see the unused-stubbing report instead. The fix is in the matchers or the test data, never `lenient()`. ## Why not just turn strictness off Switching the class to `@MockitoSettings(strictness = Strictness.LENIENT)` makes the message disappear and also disables argument-mismatch detection for every mock in the class. You trade a two-minute cleanup for permanently worse diagnostics: the next wrong-argument bug in that class will surface as a `NullPointerException` in unrelated code. Class-wide leniency is defensible as a temporary, commented migration marker; it is not a fix. ## Reading the message well The stack line points at the *stubbing*, not at the failure. If several stubbings are unused, Mockito lists them all, which is often the fastest way to see that one shared fixture is the source. If the exception appears only when the whole class runs and not for a single test, suspect state shared across tests (a static mock, a mock field initialised once) rather than the individual test. ## A subtlety about verification A stubbing counts as used when the mock is *called* in a matching way — not when you `verify()` it. Verifying a call that never happened fails with a verification error instead. Conversely, under strict stubbing a call that matched a stubbing is treated as already verified, so `verifyNoMoreInteractions` will not force you to verify stubbed calls. ## Interview framing Say what the exception means in one sentence, name the shared-`@BeforeEach` case as the usual real-world cause, and make clear that your first instinct is to delete or relocate the stubbing rather than to relax strictness.

  • Why does the same test class pass under JUnit 4's MockitoJUnitRunner but fail with UnnecessaryStubbingException under JUnit 5's MockitoExtension?
    The scope of the check differs. The plain JUnit 4 runner collects unused stubbings across the entire class and reports once at the end, so a @Before stubbing used by any single test counts as used. The JUnit 5 extension opens a session per test method, so every test that does not exercise that stubbing fails individually.
  • Does calling verify() on a stubbed method count as using the stubbing?
    No. A stubbing is marked used when the mock is actually invoked in a way that matches it during the test. verify() only asserts afterwards that such an invocation happened — if it did not, you get a verification failure rather than a satisfied stubbing. So you cannot silence UnnecessaryStubbingException by adding a verify().

saying these in an interview costs you the question

  • Fixing it by switching the class to LENIENT instead of removing or relocating the stubbing.
  • Thinking the exception means an assertion failed or the mock was never created.
  • Believing adding a verify() marks the stubbing as used.
  • Assuming it is always reported at the end of the class — under the JUnit 5 extension it is per test method.
  • Deleting the stubbing without checking whether the test went stale because production code changed.

context