Using Mockito, how do test doubles map to stubbing (when/thenReturn) versus verification (verify), and how does this relate to the dummy/fake/stub/spy/mock taxonomy?
answer
- when/thenReturn = stub = control indirect INPUT
- verify = mock = assert indirect OUTPUT (the call happened)
- Stub queries, verify commands
- spy wraps a real object; use doReturn().when() to stub it
- @Mock + @InjectMocks + MockitoExtension
basics
~20 sMockito creates fake versions of dependencies. when(x).thenReturn(y) sets up canned answers (stubbing — controlling input). verify(x).method() checks that your code actually called a dependency (verification — checking interactions). Stubs feed data in; mocks confirm behavior.
solid answer
~50 sA test double replaces a real dependency. In the classic taxonomy: a dummy is just a placeholder passed but unused; a fake is a working lightweight implementation (e.g. in-memory repo); a stub returns canned answers to indirect inputs; a spy records calls (and may wrap a real object); a mock is pre-programmed with expectations about which calls should happen. Mockito blurs these: mock(Foo.class) gives you an object that's a stub when you do when(foo.bar()).thenReturn(x) — controlling what your code reads — and a mock/spy when you do verify(foo).save(order) — asserting your code's outgoing calls. The rule of thumb: stub queries (methods that return data, verify is redundant and brittle), verify commands (void side-effecting calls you can't observe through a return value). spy(realObject) wraps a real instance so unstubbed methods run for real. Over-verifying every interaction couples tests to implementation; prefer state-based assertions where possible.
code
java · 19 lines@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock UserRepository repo; // a Mockito double
@Mock EmailSender email;
@InjectMocks UserService service; // built with the mocks above
@Test
void renamingPersistsAndNotifies() {
// STUB a query (indirect input) — don't later verify it
when(repo.findById(1L)).thenReturn(new User(1L, "Ada"));
service.rename(1L, "Grace");
// VERIFY commands (indirect output / side effects)
verify(repo).save(argThat(u -> u.getName().equals("Grace")));
verify(email).send(eq("Grace"), contains("renamed"));
verifyNoMoreInteractions(email);
}
}go deeper
Can create a mock, stub it with when/thenReturn, and verify a call. Knows mocks replace real dependencies for isolation.
Maps when/thenReturn to stubbing (input) and verify to interaction checking (output), uses @Mock/@InjectMocks, and knows argument matchers (eq, any, argThat).
Applies 'stub queries, verify commands', avoids over-verification, uses spies judiciously, and chooses state- vs interaction-based testing per case.
Sets mocking guidelines, recognizes excessive mocking as a design smell (high coupling, missing abstractions), and steers toward fakes/contract tests where mocks would be brittle.
## Why we need test doubles When you unit-test a class, it usually **depends** on other classes — a repository, an email sender, a clock. Calling the real ones makes the test slow, non-deterministic, or destructive (sends real email). A **test double** is a stand-in object you pass in place of the real dependency so the test stays fast, isolated, and repeatable. ("Double" as in *stunt double*.) ## The classic taxonomy (Meszaros / Fowler) These five terms describe *what role* a double plays — they are language-neutral; Mockito is just Java's tool for creating them: | Double | What it does | Example | |---|---|---| | **Dummy** | Passed to satisfy a parameter but never actually used | a `null`-ish placeholder object for an unused argument | | **Fake** | A real, working, but simplified implementation | an in-memory `Map`-backed repository instead of a database | | **Stub** | Returns hard-coded answers to calls (controls **indirect input** — data flowing *into* your code under test) | `userRepo.findById(1)` always returns a fixed `User` | | **Spy** | A stub that also **records** how it was called (so you can assert calls afterward); may wrap a real object | records that `email.send()` was called twice | | **Mock** | Pre-programmed with **expectations**: which calls *must* happen, verified as part of the test | expects `audit.log()` to be called once | The key conceptual split is **state vs interaction**. A **stub** is about *indirect input* — feeding data into the system under test. A **mock/spy** is about *indirect output* — observing that the system under test produced the right *outgoing* calls to its collaborators. ## Mockito blurs the lines (deliberately) Mockito calls everything a `mock`, and the *same object* acts as a stub or a mock depending on which API you use on it: ```java // Create a double for the UserRepository interface UserRepository repo = mock(UserRepository.class); // STUBBING: control indirect INPUT (what the code reads back) when(repo.findById(1L)).thenReturn(new User(1L, "Ada")); UserService service = new UserService(repo); service.rename(1L, "Grace"); // VERIFICATION: assert indirect OUTPUT (what the code called) verify(repo).save(argThat(u -> u.getName().equals("Grace"))); ``` - `when(repo.findById(1L)).thenReturn(...)` is **stubbing**: you program a canned answer. This makes `repo` a **stub** for that method. Variants: `.thenThrow(...)`, `.thenAnswer(...)`, and for void methods `doNothing()/doThrow()...when(mock).method()`. - `verify(repo).save(...)` is **verification**: after acting, you assert the code *did* call `save` with the right argument — making `repo` act as a **mock**. Variants: `verify(repo, times(2))`, `never()`, `verifyNoMoreInteractions(repo)`. ## When to stub vs when to verify A durable rule (from "Mocks Aren't Stubs"): - **Stub queries, don't verify them.** A *query* returns data and has no side effect (`findById`). You stub it to feed input; you should **not** also `verify(repo).findById(1L)` — whether it's called once or cached is an implementation detail, and verifying it makes the test brittle. Assert on the *result* instead (state-based testing). - **Verify commands.** A *command* changes state or talks to the outside world and often returns `void` (`repo.save`, `emailSender.send`, `auditLog.record`). You can't observe its effect through a return value, so verifying the call is the legitimate way to test it (interaction-based testing). Over-using `verify` couples your test to *how* the code works rather than *what* it produces, so refactoring breaks tests that should still pass. Prefer state assertions; reach for `verify` for genuine side effects. ## Spy — wrapping a real object ```java List<String> real = new ArrayList<>(); List<String> spy = Mockito.spy(real); spy.add("x"); // runs the REAL add verify(spy).add("x"); // and records it when(spy.size()).thenReturn(99); // selectively override ``` A **spy** calls through to the real object for unstubbed methods but lets you record/override specific ones. Useful for partial doubles of legacy classes, but a heavy spy often signals the design should be refactored. (Note: stubbing a spy needs `doReturn(...).when(spy).method()` to avoid actually invoking the real method while stubbing.) ## Wiring with annotations With `@ExtendWith(MockitoExtension.class)`, fields annotated `@Mock` are auto-created and `@InjectMocks` builds the system-under-test with those mocks injected — reducing boilerplate. ## The takeaway mapping The taxonomy (dummy/fake/stub/spy/mock) is the *vocabulary*; `when/thenReturn` is Mockito's way to make a **stub**; `verify` is Mockito's way to make a **mock/spy** assertion. Knowing which to use is really the universal *state-vs-interaction* testing decision wearing Java syntax.
- Why is it usually a smell to both stub and verify the same query method?Because a query has no side effect, so whether the code calls it once, twice, or caches it is an implementation detail. Verifying the call couples the test to internals; you should instead stub it for input and assert on the produced result (state-based testing).
- Why does stubbing a Mockito spy require doReturn().when() instead of when().thenReturn()?Because when(spy.method()) actually invokes the real method first (the spy delegates to the real object), which may throw or cause side effects. doReturn(x).when(spy).method() avoids the real call while setting up the stub.
A stub is a script fed to an actor ('when asked the time, say noon') — controlling what flows in. Verification is the director afterward checking the call sheet: 'did the actor actually deliver line 3?' — confirming the outgoing action happened.
saying these in an interview costs you the question
- Verifying every interaction (over-specification), making tests break on harmless refactors
- Verifying query methods that should just feed input — assert on the result instead
- Thinking a Mockito 'mock' and a 'stub' are different objects — the same mock acts as either depending on when() vs verify()
- Stubbing a spy with when().thenReturn() and accidentally invoking the real method
- Mocking value objects / DTOs instead of constructing them — only mock collaborators with behavior