Mockito offers lenient() on a single stubbing and a lenient setting on a whole mock. How do these differ in scope, and when is suppressing a strict-stubbing failure legitimate rather than a sign of a bad shared fixture?
answer
- lenient() stub < lenient mock < LENIENT class
- doReturn(x).lenient().when(mock).foo()
- leniency kills BOTH checks, incl. arg mismatch
- legit: optional background fixture, parameterized subsets
- smell: fat @BeforeEach, red-build-driven lenient()
basics
~20 slenient().when(...) exempts one stubbing; @Mock(lenient = true) or withSettings().lenient() exempts every stubbing on that mock. Suppression is legitimate for a genuinely optional shared fixture stub. It is a smell when a fat setup block stubs everything so tests can be short — fix the fixture instead.
solid answer
~60 sThree scopes, narrowest first: - `lenient().when(mock.foo()).thenReturn(x)` and `doReturn(x).lenient().when(mock).foo()` — that **one stubbing** is exempt from unused-stubbing and argument-mismatch checks. - `@Mock(lenient = true)` or `Mockito.mock(Foo.class, withSettings().lenient())` — **every stubbing on that mock**. - `@MockitoSettings(strictness = Strictness.LENIENT)` — the whole class; a blunt instrument. Legitimate suppression looks like: a clock or config mock whose stubbed value is background for the whole class but only read by some tests; a parameterized class where each parameter set exercises a different subset of stubbings; a builder-style fixture giving a collaborator sane defaults that individual tests override. It is a smell when the `@BeforeEach` stubs a collaborator's whole surface so every test is two lines, when the exemption is added purely to make a red build green, or when the same mock is lenient in a dozen classes. The real fixes are moving stubbing into the tests that need it, extracting intention-revealing helpers (`givenUserExists(...)`), or splitting a test class that is covering two behaviours. Rule of thumb: relax at the narrowest scope, and leave a comment saying why.
code
java · 13 lines@ExtendWith(MockitoExtension.class)
class PricingServiceTest {
@Mock(lenient = true) Clock clock; // background: only some tests read time
@Mock DiscountRepository discounts; // asserted on: stays strict
@InjectMocks PricingService service;
@BeforeEach
void setUp() {
when(clock.instant()).thenReturn(Instant.parse("2024-01-01T00:00:00Z"));
lenient().when(discounts.defaultRate()).thenReturn(BigDecimal.ZERO); // default, often overridden
}
}go deeper
Know the two narrow forms — lenient() on a stubbing and @Mock(lenient = true) — and that they are exceptions, not defaults.
Explain the three scopes precisely and that leniency also disables argument-mismatch detection.
Lead with the judgment: name the legitimate cases (optional background fixture, parameterized subsets, defaults-with-override) and the smells (fat setup, red-build-driven leniency, one class covering two behaviours).
Read spreading leniency as a design signal about oversized mocked types and fixture strategy, and set the team rule for how exceptions are documented and reviewed.
## The three scopes **Per stubbing.** `Mockito.lenient()` returns a stubber whose single stubbing is exempt: ```java lenient().when(clock.instant()).thenReturn(Instant.EPOCH); doReturn(0L).lenient().when(counter).count(); // the doReturn style ``` Everything else on that mock stays strict. This is the tool you should reach for first, because it documents *exactly* which instruction is optional. **Per mock.** `@Mock(lenient = true)` on a field, or `Mockito.mock(Foo.class, withSettings().lenient())` for an inline mock, exempts every stubbing on that mock, present and future. Right when the mock as a whole is background infrastructure (a clock, a feature-flag service, a config holder) rather than a collaborator the test is asserting about. **Per class.** `@MockitoSettings(strictness = Strictness.LENIENT)` switches off both hygiene checks for all mocks in the class. It is the correct answer roughly never, and an acceptable *temporary* migration marker with a ticket. Note the asymmetry: leniency only relaxes. There is no way to make one stubbing strict inside a lenient class, so keeping the class strict and relaxing narrowly always preserves more signal. ## What leniency actually switches off Both checks, for the exempted scope: - no `UnnecessaryStubbingException` if the stubbing goes unused, - no `PotentialStubbingProblem` if the code calls that method with non-matching arguments — the call falls back to the default answer. The second half is the part people forget, and it is why blanket leniency is expensive: you are not just silencing a tidy-up warning, you are re-enabling silent `null` returns for that scope. ## When suppression is legitimate **Genuinely optional background fixture.** A `Clock`, `FeatureFlags` or `MessageSource` mock stubbed once so the class has deterministic behaviour, read by some tests and not others. Duplicating that into ten tests adds noise without adding meaning. Mark the mock lenient with a one-line comment. **Parameterized / data-driven tests.** With `@ParameterizedTest`, each parameter set can legitimately exercise a different branch, so a shared stubbing is used on some runs and not others. Per-stub or per-mock leniency is the intended answer; the alternative is a `switch` in setup, which is worse. **Defaults-plus-override fixtures.** A helper that gives a collaborator sane defaults which individual tests override with a more specific stubbing. The default stubbing is by design not always used. **Legacy quarantine during a migration.** A class you are not rewriting today, with a comment and a ticket. Temporary, tracked, and ideally at mock scope rather than class scope. ## When it is a shared-setup smell **The fat `@BeforeEach`.** Twenty lines stubbing five collaborators so each test is two lines. Every reader now has to hold the whole fixture in their head to understand any one test, and the leniency hides which stubbings each test actually depends on. The fix is to move stubbing into the tests, or extract named helpers (`givenUserExists(1L)`, `givenPaymentDeclined()`) that tests call explicitly — the arrange step becomes visible without becoming verbose. **Leniency added to fix a red build.** If the sequence was "test failed → add `lenient()` → green", nobody asked *why* the stubbing was unused. It might be a stale test whose production behaviour changed. **Leniency spreading.** The same mock lenient across a dozen classes usually means the mocked type is too big — a god service or a repository with thirty methods. The signal is about production design, not about the tests. **Leniency on the collaborator under assertion.** If the test's whole point is that the service consults the repository, a lenient repository mock removes exactly the check you wanted. **One class, two behaviours.** When half the tests need fixture A and half need fixture B, the honest fix is two test classes (or `@Nested` groups with their own setup), not one lenient superset. ## A practical decision rule When a strict-stubbing failure appears, ask in order: 1. Is this stubbing dead? → delete it. 2. Did production behaviour change? → fix the test's assertions, not the stubbing. 3. Is it needed by *most* tests here and optional for a few? → per-stub or per-mock leniency, with a comment. 4. Is it needed by *few* tests? → move it into those tests or a helper. 5. Am I about to make the class lenient? → stop; that is a fixture-design decision, not a fix. ## Interview framing Show you know all three scopes and that leniency disables argument-mismatch detection too, then spend most of the answer on the judgment: the narrow escape hatches are fine and intended, but reaching for them reflexively converts a diagnostic into noise, and a spreading pattern of leniency is usually telling you the fixture — or the mocked type — is too big.
- Besides silencing unused stubbings, what else does marking a mock lenient give up?Argument-mismatch detection. A lenient mock will not raise PotentialStubbingProblem, so a call with arguments that match no stubbing quietly returns the default value — null, 0, false — and the test fails later somewhere unrelated or passes vacuously. That is the hidden cost of using leniency as a reflex.
- Your team keeps marking the same repository mock lenient in a dozen test classes. What does that tell you?Usually that the mocked type is too large, so every test needs a broad fixture of which each test uses a slice. The signal is about production design — a repository or service with a huge surface — and the durable fix is splitting the interface or introducing a narrower port for the consumer, not more leniency.
saying these in an interview costs you the question
- Believing lenient() only suppresses the unused-stubbing report and keeps argument-mismatch checks.
- Adding lenient() as the first response to any strict-stubbing failure.
- Making the whole class LENIENT to fix one stubbing.
- Marking the very collaborator the test asserts on lenient.
- Treating a fat shared @BeforeEach as good practice because it keeps tests short.