What is strict stubbing in modern Mockito, and what problems (UnnecessaryStubbingException, PotentialStubbingProblem) does it surface that lenient stubbing hides?
answer
- STRICT_STUBS = default with MockitoExtension/Runner
- UnnecessaryStubbingException = declared but never used
- PotentialStubbingProblem = called with non-matching args
- lenient() opts out per-stub for shared setup
- plain openMocks() with no extension = lenient (checks lost)
basics
~20 sStrict stubbing (the default now) flags stubs your test never used and warns when you call a stubbed method with arguments that don't match what you stubbed. It catches dead and mismatched stubs that the old lenient mode silently ignored.
solid answer
~50 sModern Mockito (via MockitoJUnitRunner/MockitoExtension or strictness STRICT_STUBS) enforces strict stubbing, which adds two safeguards. First, UnnecessaryStubbingException fails the test if a stub you declared was never invoked — catching copy-paste leftovers, dead stubs, and tests that drifted from the code. Second, PotentialStubbingProblem warns when the code under test calls a stubbed method with arguments that don't match the stub's argument matchers, which usually means a typo or a wrong expectation rather than intended fall-through. Strict stubbing also makes argument-matching mismatches loud instead of silently returning a default. The benefit is tests that fail fast on stale or wrong setup, improving signal. When a stub is intentionally conditional (shared setup used by only some tests), you opt out per-stub with lenient() (or lenient mocks) rather than disabling strictness globally. Lenient mode (legacy default) ignored all of this, letting rotten stubs accumulate.
go deeper
Aware that Mockito can complain about an 'unnecessary stubbing' and that it means a stub wasn't used.
Can resolve UnnecessaryStubbingException by removing dead stubs or applying lenient(), and knows MockitoExtension enables strict mode.
Explains both UnnecessaryStubbingException and PotentialStubbingProblem, configures strictness correctly, and uses lenient() judiciously rather than globally.
Treats strict stubbing as a suite-quality lever, sets team conventions for narrow stubbing, reads heavy lenient() use as an over-mocking smell, and steers toward fakes where stub conditionality keeps fighting strictness.
## Background: strictness levels Every Mockito mock runs under a **strictness** level that governs how unused or mismatched stubs are treated: - **LENIENT** — the old behaviour: anything goes; unused/mismatched stubs are silent. - **WARN** — prints warnings but doesn't fail. - **STRICT_STUBS** — the **modern default** when you use `MockitoExtension` (JUnit 5) or `MockitoJUnitRunner` (JUnit 4): unused/mismatched stubs **fail the test**. Strict stubbing exists because lenient tests rot: stubs that no longer matter linger, hide intent, and let bugs slip through a silent default. ## Problem 1: UnnecessaryStubbingException If you declare a stub that the test **never invokes**, strict mode throws `UnnecessaryStubbingException` at the end of the test: ```java when(repo.find(1)).thenReturn(a); // but the code path never calls find(1) // -> UnnecessaryStubbingException: this stubbing was never used ``` Why it's valuable: an unused stub usually signals one of — a copy-pasted setup block, a refactor that changed the code path, a wrong argument, or a test asserting against behaviour that no longer runs. Lenient mode would pass, leaving a misleading, dead stub forever. ## Problem 2: PotentialStubbingProblem (argument mismatch) If the code under test calls a **stubbed method** but with arguments that **don't match** the stub, strict mode throws `PotentialStubbingProblem`: ```java when(repo.find(1)).thenReturn(a); // code calls repo.find(2) // -> PotentialStubbingProblem: argument mismatch ``` In lenient mode `find(2)` would silently return `null` (the default), and your test might fail far away with a confusing NullPointerException — or worse, pass for the wrong reason. Strict mode points straight at the mismatched stub, which is almost always a typo or a wrong expectation. ## Opting out: lenient(), not turning it all off Sometimes a stub is **legitimately conditional** — a shared `@BeforeEach` sets up a collaborator that only *some* tests in the class exercise. That stub is "unnecessary" for the others. The right fix is to mark **just that stub** lenient, not to abandon strictness: ```java lenient().when(repo.find(1)).thenReturn(a); ``` or make a specific mock lenient via `@Mock(strictness = Strictness.LENIENT)` / `@MockitoSettings(strictness = LENIENT)`. Disabling strictness wholesale throws away the safety net for the whole class. ## Why a principal cares Strict stubbing is a **test-suite quality lever**: it keeps the suite honest as the code evolves, surfaces dead setup during refactors, and prevents silent-default bugs. But it also pressures test design — heavy reliance on `lenient()` is a smell that a class is over-mocked or that setup is too shared. The architectural guidance is: prefer narrow, per-test stubbing; treat `lenient()` as an explicit, rare exception; and consider real **fakes** when stub conditionality keeps fighting strictness. ## How strictness gets enabled - JUnit 5: `@ExtendWith(MockitoExtension.class)` → STRICT_STUBS by default. - JUnit 4: `@RunWith(MockitoJUnitRunner.class)` (the `Strict` variant) or `MockitoRule` with `Strictness.STRICT_STUBS`. - Override per class: `@MockitoSettings(strictness = Strictness.LENIENT)`. If you set up mocks with plain `MockitoAnnotations.openMocks(this)` and no runner/extension, you get lenient behaviour and lose these checks — a common reason teams "don't see" strict stubbing. ## Mental model Lenient stubbing is a kitchen where unused ingredients pile up and a mis-measured one just disappears into the dish unnoticed. Strict stubbing is a chef who stops you: "you prepped this and never used it" (UnnecessaryStubbing) and "you reached for salt but grabbed sugar" (PotentialStubbingProblem) — catching mistakes before they reach the plate.
- A shared @BeforeEach stubs a collaborator that only half the tests use, causing UnnecessaryStubbingException in the others. What's the right fix?Mark that specific stub lenient with lenient().when(...).thenReturn(...) (or make that mock lenient), so it's exempt from the unused-stub check without disabling strictness for the whole class. Even better, move the stub into only the tests that need it so setup reflects actual usage.
- Under strict stubbing, you stub find(1) but the code calls find(2). What happens and why is that better than lenient mode?Strict mode raises PotentialStubbingProblem pointing at the argument mismatch. In lenient mode find(2) would silently return null, likely surfacing later as a confusing NullPointerException or a wrongly-passing test; strict mode pinpoints the real cause immediately.
saying these in an interview costs you the question
- Disabling strictness globally to 'fix' an UnnecessaryStubbingException instead of removing the dead stub or using lenient() per-stub.
- Assuming a stubbed method with different args silently returns null under strict mode — it raises PotentialStubbingProblem.
- Thinking strict stubbing is on automatically even with plain MockitoAnnotations.openMocks and no runner/extension.
- Treating frequent lenient() as normal rather than a smell of over-shared setup or over-mocking.