Core Java · Unit Testing
How to test a class whose real dependencies are slow, remote, or out of your control — by standing in fakes, stubs, and mocks in their place.
Problem: a test should reveal one unit’s behaviour without a database or network deciding its speed. Plan 35 minutes for a small Mockito exercise or 15 minutes for interview practice.
Recall first: name the dependency of the class under test; prior constructor injection helps but is not a gate.
A dummy fills a parameter, a stub supplies a response, a fake has a working lightweight implementation, a spy observes a real object, and a mock verifies an interaction. The test chooses the smallest double that makes the behaviour observable.
Closest confusion: stubbing controls what a dependency returns; verifying checks that an important message was sent. A mock is not automatically better than state-based assertions.
Failure trace: stubbing findById(1L) then calling findById(2L) silently returns Mockito’s default, often producing a misleading null failure.
Decision rule: mock at a boundary you do not own, assert output when it expresses the contract, and verify interaction only when the interaction itself matters.
Version note: examples use current Mockito style reviewed with Spring Boot 4.x material on 2026-08-28.
Mission (25 minutes): write a test for an order service that reads a repository and sends a notification only after a valid order. Likely files: service test, repository mock, and notification port.
Acceptance checks: the valid case returns a value and verifies one notification; the absent case throws or returns a clear result and sends none; no Spring context starts.
Keep fixtures readable: arrange one input and one collaborator response, act once, then assert the output and the consequential interaction.
Transfer: replace the mock repository with an in-memory fake and decide which test now better communicates the rule.
Two-minute summary: doubles are tools for control and observation; the choice depends on whether the test needs a value, a lightweight implementation, or proof of a collaboration.
Explain in 60 seconds: when would you prefer a fake over a mock?
Primary question: distinguish mock, stub, spy, fake, and dummy. Follow-ups: what makes a Mockito test brittle, and why should you not mock the class under test?
Debug exercise: a stubbed repository returns null unexpectedly. Compare the stubbed arguments with the invocation and propose a safer matcher only where it is meaningful.
Define each double through its role, give one small example, and tie verification to an externally meaningful side effect rather than interaction counting.
Timed mock: describe a fast test strategy for a service with a database and email dependency in 90 seconds.
Learn next: Use distinguish test doubles as the base, then write a service test in Proving it works: Mockito & slice tests. Interview next: Use distinguish test doubles as the base, then separate authentication from authorization in Spring Security — Basic Auth.
Continue this path · Switch paths from this topic · Review first: constructor injection
A unit test should exercise your code, not the network, the database, or someone else's API. When a class leans on an external dependency, you swap that dependency for a stand-in you control — a test double. Fakes, stubs, and mocks are three flavors of stand-in, and Mockito is the framework that lets you conjure the last two in a single line instead of hand-writing a class.
Why unit test
The 3 test doubles
verify() how/when/with-what it was called
Ladder of pain (why we climb)
Mockito core API
mock(Class.class) → create the doublewhen(m.method(args)).thenReturn(x) → stub the answerverify(m).method(args) → assert it was calledAnnotation style (what you actually write)
@ExtendWith(MockitoExtension.class) → enable Mockito on class@Mock → = mock(Foo.class) for that field@InjectMocks → builds class-under-test + injects all @Mocks (via
setter/ctor)Args & dynamic returns
any() · anyDouble() · eq(x) → which calls
the stub answerseq())
doAnswer(a).when(m).f(...) → return computed from real argsinvocation.getArgument(0) inside Answer<T>; use
for void / arg-dependentPitfalls to box
UnnecessaryStubbingException → stub set but never called → remove itnew inside method overwrites injected mock → real API called (let
@Autowired own it)
any(), then tighten → step that breaks = bad argReading a mock test (template)
Markers
when() that's never hit =
UnnecessaryStubbing
eq() + raw value in same call| Double | Has real logic? | Verifies calls? |
|---|---|---|
| Fake | yes (lightweight) | no |
| Stub | no (canned) | no |
| Mock | no (canned) | yes |
Google Maps shows Uber price estimates. To get them, it calls Uber's live pricing API and hands the numbers to users. One afternoon Uber's service starts returning null and negative prices for a handful of routes, and Google Maps crashes for those users. The team patches the code to handle the bad values — but now comes the hard part. How do you test that fix when the buggy responses are rare, live, and owned by a company that has no interest in helping you reproduce them?
You cannot call the real Uber API in a test: it is slow, it costs money, it needs the network, and you cannot force it to return the exact broken data you are guarding against. This single problem — a class you want to test depends on something you do not control — is what the entire idea of test doubles exists to solve.
A unit test checks one small piece of code — usually a single class or method — in isolation from everything else. The common objection is that writing them is a tax on an already-packed schedule, and that solid code does not need them. In practice the opposite holds: tests increase development speed. They catch regressions the moment you introduce them, and they let you refactor aggressively because a green test suite tells you nothing broke.
A unit test is a smoke detector wired to one room. It does not tell you the whole house is safe — it tells you, instantly and precisely, that this room just caught fire. The cost is a battery check now and then; the payoff is that you find the fire while it is small.
The word "unit" is the whole tension. A unit is one class. But almost no useful class lives
alone — it calls a database, a payment gateway, another team's service. If your test of
GMapService secretly reaches across the internet to Uber, you are no longer unit testing;
you are testing Uber, the network, and your code all at once, and any of the three can make the test
fail.
The clean seam that makes all of this possible is that GMapService depends on an
interface (UberRESTHandler), not a concrete class. In production you inject
the real implementation; in a test you inject a stand-in. This is dependency injection doing the
heavy lifting — test doubles are only easy because the design left a seam to swap at.
A fake is a genuine working implementation of the dependency, but a lightweight one that lives inside your test instead of out in the world. It really does the job — it just does it locally and simply. The classic example is swapping a production database for an in-memory collection: same behavior (store, fetch), none of the network, disk, or setup cost.
A fake is a stunt double. A real, capable person who actually performs the action — jumps, runs, falls — standing in for the star when using the star is too expensive or dangerous. It genuinely does the work; it is just not the one you ship.
For the Uber problem, a fake means building a small fakeuberapi.com that returns
hand-crafted responses, including the null and negative prices you want to test against. It works, and
it lets you rehearse corner cases on demand. But two problems surface immediately.
@Test
public void testGetPriceEstimates() {
double startLat = 37.775231, startLng = -122.418075;
double endLat = 37.775241, endLng = -122.518075;
// A Fake: a real handler pointed at a lightweight fake endpoint
UberRESTHandler externalUberService = new RealUberRESTHandler("https://fakeuberapi.com");
GMapService gMapService = new GMapService(externalUberService);
PriceEstimate[] actual = gMapService.getPriceEstimates(startLat, startLng, endLat, endLng);
assertEquals(5, actual.length);
}
A fake is real code, so it is real maintenance. When Uber changes its response format, you now have two implementations to update, and a subtle bug in your fake can make a broken test pass or a correct one fail. Worse: can you even faithfully fake a system as large as Uber's? The fake is only as trustworthy as your ability to reproduce the original.
A stub is an object that returns pre-configured answers when its methods are called, and does nothing else. There is no working implementation behind it — no in-memory database, no parsing, no logic. When the code under test asks the stub for price estimates, the stub simply hands back a canned list you decided on in advance (often read from a JSON file in the test's resources folder).
A stub is a cardboard cutout of a shopkeeper with a fixed sign around its neck: "Today's price: $12." Ask it anything and it shows you the sign. It cannot compute, negotiate, or react — but for a photo shoot where you only need the sign visible, that is exactly enough.
Stubs shine in the scenario fakes struggle with. Suppose Uber announces a new response format that ships in a month. You implement your side against the proposed format now — but there is no live service returning it yet, and editing your elaborate fake to match is costly. A stub sidesteps all of that: you just declare "when asked, return a response in the new shape," scoped to this one test.
@Test
public void testGetPriceEstimates() {
double startLat = 37.775231, startLng = -122.418075;
double endLat = 37.775241, endLng = -122.518075;
// A Stub: no real logic, just pre-configured responses
UberRESTHandler externalUberService = new StubUberRESTHandler();
GMapService gMapService = new GMapService(externalUberService);
// When getPriceEstimates() calls the stub, it returns the
// canned response (e.g. read from a JSON file in resources).
PriceEstimate[] actual = gMapService.getPriceEstimates(startLat, startLng, endLat, endLng);
assertEquals(5, actual.length);
}
A stub is lighter than a fake, but you still have to hand-write a class —
StubUberRESTHandler — and often a new one per test scenario. That is a lot of
boilerplate for "just return this list." Which is where mocks, and Mockito, come in.
A mock is a more sophisticated stub: it still returns pre-fabricated values, but it also records every call it receives, so you can afterward verify how it was used. Was the dependency called? How many times? In what order? With what arguments? A stub answers "what did my code get back"; a mock also answers "what did my code do."
A mock is a cardboard shopkeeper with a hidden clipboard. It still shows the fixed price sign (the stub part), but it quietly writes down every question you asked, when, and how many times. Later you read the clipboard to confirm you actually asked what you were supposed to.
The canonical reason you need this: a method whose whole job is a side effect. Think of code that sends a confirmation email. There is no return value to assert on — success means "the email service was called exactly once with the right address." Only a mock can check that, because only a mock records the call.
Stubs verify state; mocks verify behavior. A stub feeds your code an input and you assert on the output. A mock lets you assert on the interaction itself — that a particular method was called, correctly. If a question hinges on a side effect with no return value, the answer is a mock.
Writing a mock class by hand would be even more work than a stub — it has to record calls too. So nobody does. You use a mocking framework: EasyMock, PowerMock, and most commonly Mockito. The framework generates the double, the stubbed answers, and the recording machinery for you, at runtime, in a few lines.
Mockito's entire everyday surface is three static methods. Learn these and you can read and write the majority of mock-based tests.
| Verb | What it does | Role |
|---|---|---|
| mock(C.class) | creates a fake instance of class/interface C | make the double |
| when(...).thenReturn(x) | "when this method is called, hand back x" | the stub part |
| verify(m).method(...) | asserts that method was called on the mock | the mock part |
@Test
public void testGetPriceEstimates() {
double startLat = 37.775231, startLng = -122.418075;
double endLat = 37.775241, endLng = -122.518075;
List<PriceEstimate> estimateList = someListOfPriceEstimates(); // canned data
// 1. create the mock
UberRESTHandler externalUberServiceMock = mock(RealUberRESTHandler.class);
GMapService gMapService = new GMapService(externalUberServiceMock);
// 2. stub its response
when(externalUberServiceMock.fetchEstimates(startLat, startLng, endLat, endLng)).thenReturn(estimateList);
// 3. exercise the code under test
PriceEstimate[] actual = gMapService.getPriceEstimates(startLat, startLng, endLat, endLng);
// check the returned value
assertEquals(5, actual.length);
// 4. verify the dependency was actually called
verify(externalUberServiceMock).fetchEstimates(startLat, startLng, endLat, endLng);
}
Always mock the dependency, never the class you are testing. Here you mock
UberRESTHandler (the thing GMapService leans on) and leave
GMapService real. If you accidentally mock the class under test, your assertions check
the framework's canned answers, not your actual logic — the test turns green while proving
nothing.
A when(...).thenReturn(...) whose arguments never match the real call is silently dead.
Mockito returns a default (null, 0, or empty) instead of your canned value, and the test may still
pass for the wrong reason. Make the stubbed arguments match what the code under test actually passes
— or use argument matchers deliberately.
Mock tests get dense fast. A reliable way to decode one — or to plan your own before writing it — is to name six things explicitly. Fill this in for any test and the structure falls out on its own.
gMapService.getPriceEstimates() — the one method whose behavior you are
actually checking.
In plain words: returns a list of price estimates given valid start/end coordinates.
externalUberServiceMock.fetchEstimates() — the call you intercept.
Function to mock + the input to match + the pre-set output to return.
The coordinates you pass into the method under test.
The list of price estimates you assert on — plus the verify that the
dependency was called.
All three replace a real dependency. They differ in how much they actually do, and in whether they let you inspect how they were used.
| Fake | Stub | Mock | |
|---|---|---|---|
| Working logic? | Yes, lightweight | No | No |
| Returns canned data? | Computed | Yes | Yes |
| Records / verifies calls? | No | No | Yes |
| Effort to build | High | Medium | Low (framework) |
| Answers | state | state | state + behavior |
| Best when | need real-ish behavior locally | need a fixed response | need to check an interaction / side effect |
You care only about what your code gets back. Feed a known input, assert on the output. No interest in whether or how the dependency was called.
You care about the interaction. The dependency's job is a side effect (send email, write log, publish event) with nothing to assert on but the call itself.
The mock(Class.class) factory call is the honest, explicit way to see what is happening
— but in a real Spring project you almost never write it. Mockito ships three annotations that do
the same work declaratively, wired up once at the top of the test class. This is the form the take-home
exercise actually uses, and the form an interviewer expects you to recognize.
| Annotation | What it does |
|---|---|
| @ExtendWith(MockitoExtension.class) | registers the test class with Mockito (JUnit 5) so the annotations below are processed |
| @Mock | creates a mock of that field — the declarative equivalent of
mock(Foo.class)
|
| @InjectMocks | builds the class under test and injects every matching @Mock into it
automatically |
@InjectMocks is a stagehand who dresses the actor before the scene. You declare the
props (the @Mock fields) and the actor (@InjectMocks field); the stagehand
quietly hands each prop to the actor through the right door (the setters or constructor) before
every take, so you never wire them up by hand in each test.
@ExtendWith(MockitoExtension.class)
class ExternalUberServiceTest {
@Mock
private GoogleMapsHelper googleMapsHelperMock;
@Mock
private RestTemplate restTemplateMock;
// built by Mockito; the two mocks above are injected into it
@InjectMocks
private ExternalUberService externalUberService;
@Test
void getPriceEstimatesValidLocationReturnsPriceEstimatesTest() {
when(googleMapsHelperMock.isValidLocations(anyDouble(), anyDouble(), anyDouble(), anyDouble())).thenReturn(true);
// ... exercise externalUberService.getPriceEstimates(...) and assert
}
}
Before @InjectMocks, every test had to build the object and hand it each mock through
setters — new ExternalUberService(), then setGoogleMapsHelper(mock),
then setRestTemplate(mock). The annotation collapses all of that into one field declaration
and does the injection before every test, through those same setters.
Set a breakpoint inside the setter methods and run in debug: the debugger stops inside
setGoogleMapsHelper() and setRestTemplate() even though your test never
calls them. That is @InjectMocks pushing the mocks in through the setters for you.
Seeing it stop there is the proof that injection is real, not magic.
So far every stub returned the same value no matter what. Two refinements from the exercise make stubs far more precise: argument matchers loosen or tighten which calls a stub responds to, and doAnswer lets the return value be computed from the actual arguments.
Instead of hard-coding isValidLocations(37.77, ...) you can match by shape:
any() matches any object, anyDouble() any double, and eq(x) pins
one argument to an exact value while the others stay loose. This is also the go-to debugging move when a
stub silently never fires (see §1.10).
// matches ANY four arguments
when(googleMapsHelperMock.isValidLocations(any(), any(), any(), any()))
.thenReturn(false);
// matches any four doubles (type-aware)
when(googleMapsHelperMock.isValidLocations(anyDouble(), anyDouble(), anyDouble(), anyDouble())).thenReturn(false);
// pin the first arg exactly, leave the rest loose
when(googleMapsHelperMock.isValidLocations(eq(37.775231), anyDouble(), anyDouble(), anyDouble())).thenReturn(true);
If you use a matcher for one argument, you must use matchers for all of them. Writing
isValidLocations(eq(37.77), someLng, ...) — a matcher plus a raw value —
throws an InvalidUseOfMatchersException. Wrap the fixed one in eq(...) so
every argument is a matcher.
doAnswer() lets a mock decide its answer at call time, based on the exact arguments
it received. Where thenReturn(x) is a fixed sign, doAnswer is a
tiny function: it hands you an InvocationOnMock, you pull the arguments off it with
getArgument(i), and you return whatever those arguments imply. In the exercise this
validates a latitude — return true only when it falls within −90 to +90.
Answer<Boolean> validity = new Answer<Boolean>() {
public Boolean answer(InvocationOnMock invocation) throws Throwable {
Double startLatitude = invocation.getArgument(0); // first arg
return Math.abs(startLatitude) <= 90; // valid latitude range
}
};
// use validity when the mock's method is invoked with these arg shapes
doAnswer(validity).when(googleMapsHelperMock)
.isValidLocations(eq(37.775231), anyDouble(), anyDouble(), anyDouble());
Answer<T> is generic, which is why the return type Boolean is written
everywhere. Reach for doAnswer when the response must vary with the input, when the
mocked method returns void, or when you want a side effect on invocation. For a flat,
input-independent value, plain when().thenReturn() is simpler — don't over-reach
for doAnswer.
Mockito is fiddly to get right, but its error messages are unusually informative — the fastest debugging habit is to actually read the exception rather than re-scan your code. Three failures come up constantly in this exercise.
Mockito fails the test if a when().thenReturn() you declared is never actually invoked.
This is deliberate: an unused stub means either the method you expected to be called wasn't (a real
bug), or the stub was never needed. Fix it by removing the stub the method under test doesn't use
— e.g. stubbing isValidLocations() for a test of
buildPriceEstimateBaseUrl(), which never calls it.
A mock injected into a field is lost the moment the production code reassigns that field with
new. In the exercise, adding restTemplate = new RestTemplate() inside the
method overwrote the injected mock, the real Uber API got called, and the test failed with
an authorization error. Let Spring's @Autowired own field creation; never
new a dependency inside the method you're testing.
When a stub silently never fires and the test misbehaves, don't stare at the code — loosen the
arguments and watch when it starts working. Swap your specific values for any(),
confirm the stub now gets hit, then tighten one argument at a time (anyDouble(), then
eq(value)). The step where it breaks is the argument that didn't match the real call.
Doubtful whether the injected object is the mock or the real thing? Put a breakpoint in the dependency's method — with a mock in place, the debugger will not stop there, because the mock replaces the real implementation entirely. Or hover the field in the debugger: its type shows as a Mockito-generated proxy, not the plain class.
The exercise gives one reusable planning template — fill it in before writing a mock test and the code writes itself. It expands the six-part reading frame from §1.6 to handle multiple mocked dependencies, which real tests usually have.
Code Under Test:
Test case:
Dependency Layer/Function:
1.
2.
Mock Setup:
- Function to mock:
- Input to mock:
- Pre-set output from mock:
- Function to mock:
- Input to mock:
- Pre-set output from mock:
Test Input:
Expected Test Output:
Worked for the exercise's own test,
getPriceEstimatesValidLocationReturnsPriceEstimatesTest(), it fills in like this:
Code Under Test: ExternalUberService.getPriceEstimates()
Test case: valid coordinates return a populated PriceEstimate[]
Dependency Layer/Function:
1. GoogleMapsHelper.isValidLocations() -> validates the coordinates
2. RestTemplate.getForObject() -> fetches the price-estimate response
Mock Setup:
- Function to mock: googleMapsHelperMock.isValidLocations
- Input to mock: the start/end lat & lng coordinates
- Pre-set output from mock: true
- Function to mock: restTemplateMock.getForObject
- Input to mock: the built request URL
- Pre-set output from mock: JSON read from price_estimate.json
Test Input: valid start/end coordinates
Expected Test Output: PriceEstimate[] of length 3 with the expected fare strings
A stronger test checks the actual values, not only the array size — e.g.
assertEquals(3, actual.length) followed by
assertEquals("₹110-140", actual[0].getEstimate()). Length alone passes even when the
parsing is subtly wrong.
The follow-up questions this material naturally invites. Answers are short enough to say out loud.
A test double is any stand-in object you inject in place of a real dependency during a test. You need them because a unit test should exercise one class in isolation, but real classes call things you cannot use in a test — slow, remote, costly, or flaky dependencies like a live API or production database. The double lets you control that dependency completely.
A fake is a real but lightweight working implementation, like an in-memory database standing in for a production one. A stub has no logic; it just returns pre-configured canned responses. A mock is a stub that also records the calls it receives, so you can verify afterward that a method was called the right number of times, in the right order, with the right arguments.
Verification. A stub only feeds data back to your code; you assert on the resulting state. A mock additionally records interactions, so you can assert on behavior — that the dependency was actually invoked correctly. The one-liner: stubs verify state, mocks verify behavior.
When the method under test's important effect is a side effect with no return value — sending an email, publishing an event, writing to a log. There is nothing to assert on except "the collaborator was called correctly," and only a mock records that call.
Create the mock with mock(Dependency.class). Stub its response with
when(mock.method(args)).thenReturn(value). Exercise the real class under test,
which internally calls the mock. Finally, assert on the return value and use
verify(mock).method(args) to confirm the dependency was called as expected.
Because the class under test is the thing whose real logic you want to validate. If you mock it, you are only checking the framework's canned responses, not your code — the test passes while proving nothing. You mock the collaborators around it so the class itself runs for real against controlled inputs.
Two things. It is expensive to build and maintain — every time the real API changes format, you must update your fake too. And it may not be genuinely reproducible: faithfully faking a system as large and complex as Uber's is hard, so your fake can drift from reality and give you false confidence.
Check that the stubbed when(...) arguments actually match what the code under
test passes. If they don't match, Mockito returns a default (null/0/empty) instead of your
value, so the test can pass for the wrong reason. Also confirm you mocked the dependency,
not the class under test, and that a meaningful verify or assert
is present.
It is the seam that makes them possible. Because the class under test depends on an interface and receives its implementation from outside (via the constructor), you can inject the real one in production and a double in tests without changing the class. Without that seam, you would have no clean way to swap in a stand-in.
@ExtendWith(MockitoExtension.class) registers the test class so Mockito
processes its annotations. @Mock creates a mock for that field — the
declarative form of mock(Foo.class). @InjectMocks builds the class
under test and injects every matching @Mock into it automatically, through its
constructor or setters, before each test.
When the return value has to depend on the actual arguments the mock was called with, when
the mocked method returns void, or when you need a side effect at call time.
doAnswer gives you the InvocationOnMock, from which
getArgument(i) reads each argument so you can compute the response. For a
fixed, input-independent value, when().thenReturn() is simpler.
Mockito throws it when a when().thenReturn() you set up is never actually called
during the test. It is a safety net: an unused stub usually means the method you expected to
be invoked wasn't (a real bug) or the stub was never needed. Fix it by removing the stub the
method under test doesn't use, or by correcting why the expected call never happened.
Because the injected mock lives in that field, and reassigning it with new
replaces the mock with a real object mid-test. In the exercise, doing
restTemplate = new RestTemplate() inside the method caused the real Uber API to
be called, which failed with an authorization error. Let Spring's @Autowired
own field creation and never new a dependency inside the code under test.
mock() to create, when().thenReturn() to stub,
verify() to check the interaction.
@ExtendWith(MockitoExtension.class),
@Mock for each dependency, @InjectMocks to build and wire the class
under test automatically.
any(), anyDouble(), eq()) control
which calls a stub answers — all-or-nothing per call. doAnswer() computes the
return from the actual arguments.UnnecessaryStubbingException (unused stub), new overwriting
an injected mock, and matcher/raw-value mixing. Read the exception — Mockito's errors are
informative.Reading a mock and writing one are different muscles. Do each of these before you open the walkthrough
— sketch the test in an editor or on paper first. Struggling for a minute is what turns
"I recognise verify()" into "I can reach for it without thinking."
You're unit-testing WeatherReporter. It depends on WeatherApi, a slow
external service you must not hit in a unit test. Write a JUnit 5 + Mockito test proving that
report("Dubai") returns "Dubai: hot" when the API reports 41 °C
— and that the API was asked exactly once, for Dubai.
WeatherApi with a test double.public interface WeatherApi {
double tempCelsius(String city); // the real impl hits the network
}
public class WeatherReporter {
private final WeatherApi api;
public WeatherReporter(WeatherApi api) { this.api = api; }
public String report(String city) {
double t = api.tempCelsius(city);
return city + ": " + (t >= 30 ? "hot" : "mild");
}
}
Map the problem onto the four-step template — create → stub → exercise → verify — and each line writes itself.
Step 1 — create the double. mock(WeatherApi.class) hands you
a
stand-in whose every method returns the type's default (0.0 for a
double) until you say otherwise. No network, no real implementation.
Step 2 — stub the response.
when(api.tempCelsius("Dubai")).thenReturn(41.0). The argument
"Dubai" is load-bearing: this stub fires only when the method is called
with exactly "Dubai". Any other city falls through to the default.
Step 3 — exercise the real code. Build the real
WeatherReporter (never a mock of it) with the double injected, then call the method
you're testing. Its real logic — the >= 30 threshold and the string
formatting — runs for real.
Step 4 — assert & verify. assertEquals checks the
returned
value (state); verify(api).tempCelsius("Dubai") checks that the collaborator was
actually invoked once, with the right argument (behavior).
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
class WeatherReporterTest {
@Test
void reportsHotWhenApiSaysHot() {
// 1 · create the double
WeatherApi api = mock(WeatherApi.class);
// 2 · stub — the argument must match the real call
when(api.tempCelsius("Dubai")).thenReturn(41.0);
// 3 · exercise the REAL class under test
WeatherReporter reporter = new WeatherReporter(api);
String result = reporter.report("Dubai");
// 4 · assert state + verify behavior
assertEquals("Dubai: hot", result);
verify(api).tempCelsius("Dubai");
verifyNoMoreInteractions(api); // optional: nothing else was touched
}
}
Why it's a fair test: only the untestable network hop is replaced; your real
branching logic still executes. And because WeatherReporter takes its dependency
through the constructor, swapping in the double needs no change to the class — that seam
is dependency injection earning its keep.
Stub when(api.tempCelsius("dubai")) (lowercase) instead, and the real call
tempCelsius("Dubai") never matches it. Mockito returns the default
0.0, report returns "Dubai: mild", and the test fails
for a reason that has nothing to do with your logic. A stub only fires on an exact argument
match.
This test compiles, and its author swears it "covers the happy path." Read it, predict what happens when you run it, then fix it so it actually proves the behavior.
public class Greeter {
private final UserRepo repo;
public Greeter(UserRepo repo) { this.repo = repo; }
public String greet(long id) {
return "Hi, " + repo.findById(id).name();
}
}
@Test
void greetsUser() {
UserRepo repo = mock(UserRepo.class);
when(repo.findById(1L)).thenReturn(new User("Trinity"));
Greeter greeter = new Greeter(repo);
String msg = greeter.greet(2L); // ← asks for id 2
assertEquals("Hi, Trinity", msg);
}
Predict first. greet(2L) calls repo.findById(2L). But
the stub only answers for 1L. For any un-stubbed argument a Mockito mock
returns the type's default — and for an object return type, that default is
null.
So repo.findById(2L) returns null, and null.name() throws
a NullPointerException. The test never reaches the assertion — it errors
out. It doesn't "pass"; it crashes, which is Mockito's way of shouting that the stub never
fired.
Root cause: the stubbed argument (1L) doesn't match the exercised
argument (2L). This is the number-one silent-mock trap — a stub is keyed to
an exact argument.
@Test
void greetsUser() {
UserRepo repo = mock(UserRepo.class);
when(repo.findById(1L)).thenReturn(new User("Trinity"));
Greeter greeter = new Greeter(repo);
String msg = greeter.greet(1L); // ← now matches the stub
assertEquals("Hi, Trinity", msg);
verify(repo).findById(1L);
}
The deeper lesson. Had findById returned a String
instead of an object, the mismatch would have been quieter still: you'd get "Hi,
null" and a failed assertion rather than a crash — at least that points
at
the value. The NPE here is the loud version of the same bug. And note the other classic form:
mocking UserRepo is correct — you mock the dependency. Write
Greeter greeter = mock(Greeter.class) and you'd be testing Mockito's canned
response, not your code.