skip to content

Mockito's Strictness enum has three values: LENIENT, WARN and STRICT_STUBS. What does each one change about how a test behaves, and why was strict stubbing introduced?

level: middleimportance: must knowfreq 58%

answer

  1. LENIENT = Mockito 1.x, no checks
  2. WARN = console hints only, migration step
  3. STRICT_STUBS = unused stubbing + arg mismatch fail
  4. strict stubs also auto-verify stubbed calls
  5. level lives on extension / runner / rule / session

basics

~20 s

LENIENT does no stubbing checks (old Mockito 1.x behaviour). WARN only logs hints about unused or mismatched stubbings. STRICT_STUBS fails the test: a stubbing that was never used is an error, and calling a stubbed method with arguments matching no stubbing fails at the call site instead of returning null.

solid answer

~50 s

`org.mockito.quality.Strictness` controls stubbing hygiene and can be set per test class, per session, or per mock. - **LENIENT** — no checks. Unused stubbings are ignored, and a call matching no stubbing silently returns the type default (`null`, `0`, `false`). - **WARN** — same runtime behaviour, but Mockito logs hints ("unnecessary stubbing", "stubbing argument mismatch") at the end of the test. Nothing fails; it is the migration step. - **STRICT_STUBS** — Mockito fails the test. A never-used stubbing raises `UnnecessaryStubbingException`, and calling a stubbed method with non-matching arguments raises `PotentialStubbingProblem` right where it happens. Stubbed invocations also count as verified, so `verifyNoMoreInteractions` stops flagging them. It exists because lenient tests rot: dead stubbings pile up and mislead readers, and an argument typo becomes a `NullPointerException` far from its cause. Strict stubbing turns both into immediate, precisely located failures.

code

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

    @Mock UserRepository repo;
    @InjectMocks OrderService service;

    @Test
    void unusedStubbingFails() {
        when(repo.findById(1L)).thenReturn(new User(1L)); // never called -> UnnecessaryStubbingException
        service.countOrders();
    }

    @Test
    void argumentMismatchFails() {
        when(repo.findById(1L)).thenReturn(new User(1L));
        service.load(2L); // repo.findById(2L) -> PotentialStubbingProblem
    }
}

go deeper

for a junior

Recall the three names and that STRICT_STUBS fails tests on unused stubbings; being able to read the exception message is enough.

for a middle

Explain both failure modes, that stubbed calls count as verified, and that the level is attached to the extension/runner/session rather than set globally.

for a senior

Frame WARN as a migration step, argue why silent nulls from argument mismatch are the real cost, and know where per-mock leniency fits.

for a principal

Discuss strictness as a suite-wide quality policy: what it costs to enforce in a legacy codebase, how it interacts with shared fixtures, and what it buys in review time.

## What strictness governs A *stubbing* is an instruction to a mock: `when(repo.findById(1L)).thenReturn(user)`. In Mockito 1.x nothing checked whether that instruction was ever used, or whether the code under test called `findById` with a different argument. Mockito 2 added the `Strictness` enum (`org.mockito.quality.Strictness`) with three levels so a test can opt into those checks. ## LENIENT No stubbing checks whatsoever — the historical Mockito 1.x behaviour. Two consequences: 1. A stubbing the code never triggers is silently ignored. Nothing tells you the line is dead. 2. If the code calls `repo.findById(2L)` while you stubbed `findById(1L)`, Mockito returns the default value for the return type — `null` for objects, `0` for numbers, `false` for booleans, an empty collection is *not* returned (that is `RETURNS_SMART_NULLS`/`RETURNS_DEEP_STUBS` territory). Your test then fails somewhere else, usually with an NPE, and the real cause (wrong argument) is invisible. ## WARN Runtime behaviour is identical to LENIENT — nothing throws — but Mockito's logger prints hints at the end of the test: "Unnecessary stubbings detected" and "Argument mismatch between stubbing and actual invocation". WARN is a migration tool: you switch a legacy suite to WARN, read the console noise, clean up, then flip to STRICT_STUBS. Its weakness is that nobody reads passing-build logs, so treating WARN as a permanent state means the checks effectively do not exist. ## STRICT_STUBS The recommended level, and what Mockito's JUnit 5 extension uses by default. It adds real failures: - **Unused stubbings fail.** At the end of the test, any stubbing that was never matched by an actual invocation produces `UnnecessaryStubbingException`, naming the file and line of the stubbing. - **Argument mismatches fail at the call site.** If a stubbed method is invoked with arguments that match no stubbing for that method, Mockito throws `PotentialStubbingProblem` from inside the call, printing both the stubbed arguments and the actual ones. You get the diagnosis instead of a downstream NPE. - **Stubbed calls are treated as verified.** With strict stubs, invocations that matched a stubbing are marked verified, so `verifyNoMoreInteractions(mock)` no longer forces you to write `verify(...)` for every call you already stubbed. - **Better misuse detection**, e.g. unfinished stubbing or verification is reported earlier and with more context. ## Why it exists The motivation is test maintainability, not purity. Dead stubbings are lies in the test: a reader assumes the stubbed interaction matters, refactors around it, and wastes time. They also hide behaviour changes — if production code stops calling a collaborator, the test still passes green while the stubbing quietly goes unused. And silent `null` returns from argument mismatches are the single most common source of confusing Mockito failures, because the stack trace points at the consumer of the `null`, not at the mismatch. A secondary benefit is speed of feedback: with STRICT_STUBS, the failure message usually *is* the fix instruction ("you stubbed findById(1) but the code called findById(2)"). ## Where the level is set Strictness is not global state you set once; it is attached to the harness that manages the mocks: - JUnit 5: `@ExtendWith(MockitoExtension.class)` (STRICT_STUBS by default), tuned with `@MockitoSettings(strictness = ...)`. - JUnit 4: the runner variants (`MockitoJUnitRunner.Silent` / `.Strict` / `.StrictStubs`) or `MockitoJUnit.rule().strictness(...)`. - No framework at all: `Mockito.mockitoSession().strictness(...).startMocking()`. - Per mock, you can downgrade with `@Mock(lenient = true)` or `withSettings().lenient()`. A mock created by a bare `Mockito.mock(Foo.class)` with no runner, extension or session has no strictness enforcement — nothing is watching the lifecycle, so nothing can report at the end. ## What to say in an interview Name the three levels, state that STRICT_STUBS is the default in the JUnit 5 extension and the level you want, explain the two failures it produces, and mention WARN as the migration path for a legacy suite rather than as a destination.

  • Under STRICT_STUBS, what happens if the code calls a mock method you never stubbed at all?
    Nothing special — it returns the default value for the return type (null, 0, false) exactly as before. PotentialStubbingProblem only fires when that same method has at least one stubbing whose argument matchers did not match this call. An entirely unstubbed method is a normal default-answer call, which is why you can still get NullPointerExceptions in a strict test.
  • Does strictness change how verify() behaves?
    verify() itself is unchanged, but STRICT_STUBS marks invocations that matched a stubbing as already verified. That means verifyNoMoreInteractions() no longer fails just because you stubbed a call and did not explicitly verify it, which removes most of the historical boilerplate around that assertion.

WARN is a compiler warning; STRICT_STUBS is -Werror. Same detection, different consequence — and only the second one survives contact with a busy team.

saying these in an interview costs you the question

  • Claiming WARN fails the build — it only logs.
  • Saying strict stubbing checks that every mock method is verified; it checks stubbings, not verifications.
  • Thinking LENIENT makes unstubbed calls throw — lenient is the level that makes Mockito quietest.
  • Believing strictness is a global switch on Mockito rather than a per-class/per-session/per-mock setting.
  • Assuming an argument mismatch always throws — it only throws when the same method has another stubbing.

context