Appearance
Mock vs Stub vs Spy
Chapters 2 to 13 covered JUnit 5: how to write, run and organise tests. A real unit, though, rarely works alone: it calls other classes, databases and external services. Mockito replaces those dependencies with fakes the test controls. This chapter introduces its three core ideas.
Mocking, stubbing and spying are the three core ideas of Mockito. They are related (all three run on the same framework code, as the next chapter shows) but they are not the same thing and should not be used interchangeably.
Why mock
A unit test tests one unit in isolation. A unit is usually one public method of a class, sometimes one behavior that spans a few public methods; this depends on the team. The common practice is at least one unit test per public method.
Real classes do not live alone. They depend on other classes, external APIs, databases, message brokers, the file system. Take an order service:
java
class OrderService {
private PaymentService paymentService;
OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
boolean placeOrder(int amount) {
if (paymentService.pay(amount)) {
return true;
} else {
return false;
}
}
}
class PaymentService {
boolean pay(int amount) {
BankResponse response = ...; // calls the real bank server
return response.isPaymentSuccess();
}
}To test placeOrder, we do not want to call the bank. Whether pay works is not this test's concern; PaymentService has its own tests. Isolation means testing placeOrder only.
Mocking replaces real dependencies with fake objects (mocks) whose behavior the test controls.
Scenario 1: the payment succeeds.
java
import static org.mockito.Mockito.*;
@Test
void placeOrderSuccess() {
// a fake PaymentService
PaymentService paymentService = mock(PaymentService.class);
// the mock is given to the system under test (SUT)
OrderService orderService = new OrderService(paymentService);
// the rule: when pay(10) is called on the mock, return true
when(paymentService.pay(10)).thenReturn(true);
boolean result = orderService.placeOrder(10);
assertTrue(result);
}placeOrder(10) calls paymentService.pay(10) on the mock. A rule exists for that call, so the mock returns true without running any real code.
The arguments are part of the rule. when(paymentService.pay(10)) matches only pay(10); pay(20) has no rule and returns the default false.
Scenario 2: the payment fails. Just change the rule:
java
when(paymentService.pay(10)).thenReturn(false);
assertFalse(orderService.placeOrder(10));In neither scenario does the real pay run.
What a mock returns by default
A mock method with no rule does nothing and returns a default: false, 0, null for objects, and empty collections, Optionals and streams. The real method never runs.
What mocking solves
- Isolation. The unit is tested without its dependencies.
- Fast and not flaky. No I/O, network or database calls, so tests are fast and stable. A flaky test passes on one run and fails on the next for reasons outside the code.
- Controlled output. The test decides what a dependency returns, including edge cases and failures (an exception, a timeout) that are hard to produce for real.
- Less coupling. Without mocks, a unit test depends on the database schema, the broker, external APIs. Change the schema and unrelated unit tests fail.
Stubbing
Stubbing defines what a dependency returns (or throws) for a given call:
java
when(paymentService.pay(10)).thenReturn(true); // this is stubbing"If this method is called with these arguments, return this value (or throw this exception)." The focus is only on input and output.
Mocking
Stubbing is one part of mocking. A mock also records every call made on it, so the test can check the interaction:
- Was a method called?
- How many times?
- With which arguments?
- In what order?
java
@Test
void placeOrderSuccess() {
PaymentService paymentService = mock(PaymentService.class);
OrderService orderService = new OrderService(paymentService);
when(paymentService.pay(10)).thenReturn(true); // stubbing
boolean result = orderService.placeOrder(10);
assertTrue(result);
verify(paymentService).pay(10); // was it called?
verify(paymentService, times(1)).pay(10); // how many times?
verify(paymentService).pay(eq(10)); // with what argument?
verifyNoMoreInteractions(paymentService); // no other calls on the mock
}Why verify when the result is already right? The result alone does not prove the flow: placeOrder might return true without ever calling pay, or call it three times (charging the customer three times). Mocking covers how the system collaborates with its dependencies, not just input and output. (Verification is covered in depth in the last chapter.)
Spying
A spy is a partial mock of a real object:
- By default, it calls the real methods.
- Selected methods can be stubbed, and all calls can be verified.
java
class Calculator {
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
}java
Calculator calculator = spy(new Calculator());
when(calculator.multiply(4, 2)).thenReturn(100); // stubbing
int sum = calculator.add(4, 2); // no stub: REAL method -> 6
assertEquals(6, sum);
int product = calculator.multiply(4, 2); // stubbed -> 100
assertEquals(100, product);
calculator.multiply(5, 3); // no stub for (5, 3): real method -> 15With a stub for the call, the spy acts as a mock; without one, as the real object.
spy(obj) wraps a copy of obj: calls on the spy change the spy's state, not the original object's.
Careful: when() on a spy calls the real method
Add a print to multiply and stub it with when:
java
Calculator calculator = spy(new Calculator());
when(calculator.multiply(4, 2)).thenReturn(100); // prints "inside multiply"!
int product = calculator.multiply(4, 2); // 100, no printtext
inside multiplyThe stub works (the call returns 100), but the real multiply ran while stubbing. Java must evaluate calculator.multiply(4, 2) before it can pass its result to when(...), and on a spy that call goes to the real method. That is harmless here, but harmful when the real method has side effects or fails:
java
List<String> list = spy(new ArrayList<>());
when(list.get(0)).thenReturn("x"); // IndexOutOfBoundsException: the real get(0) runs on an empty listOn a spy, stub with doReturn instead:
java
doReturn(100).when(calculator).multiply(4, 2); // no real call
int product = calculator.multiply(4, 2); // 100
doReturn("x").when(list).get(0); // worksdoReturn(value).when(spy).method(args) passes the spy itself to when, and only then calls the method; Mockito knows a stub is being defined and does not run the real code. The next chapter shows how this works inside Mockito.
On a plain mock, when(...).thenReturn(...) is safe: a mock has no real code to run.
Summary
| Mock | Stub | Spy | |
|---|---|---|---|
| What | a fake object | a rule: this call returns that | a real object, partly faked |
| Unstubbed call | does nothing, returns a default | n/a | runs the real method |
| Checks | output and interactions (verify) | output only | output and interactions |
| Stub with | when(...).thenReturn(...) | doReturn(...).when(spy).method(...) |
Next: The puzzle above, why when() on a spy runs the real method while doReturn() does not, has its answer inside Mockito. Chapter 15 looks at how mocks, stubs and spies work internally.
Check yourself
- What does "testing in isolation" mean, and why do real classes make it hard?
- What does an unstubbed mock method return?
- After
when(paymentService.pay(10)).thenReturn(true), what doespay(20)return? - Name four things mocking solves.
- What is the difference between stubbing and mocking?
- Why verify a call when the test's result is already correct?
- What is a spy, and what does an unstubbed call on it do?
- Why does
when(spy.multiply(4, 2)).thenReturn(100)run the realmultiply? - What happens with
when(list.get(0)).thenReturn("x")on a spy of an emptyArrayList, and what should you write instead?
Answers
- Testing only the unit's own logic, without its dependencies. Real classes call other classes, APIs, databases and brokers.
- A default:
false,0,null, or an empty collection/Optional/stream. The real method never runs. false: the stub matches only the argument 10.- Isolation; fast and stable (not flaky) tests; controlled output, including edge cases and failures; less coupling to databases, schemas and external systems.
- Stubbing defines what a call returns (input and output). Mocking includes stubbing, plus checking interactions: whether, how often, with which arguments and in which order a method was called.
- The result alone does not prove the flow: the dependency might not have been called, or been called several times or with the wrong arguments.
- A partial mock of a real object. Unstubbed calls run the real method.
- Java evaluates the argument
spy.multiply(4, 2)before callingwhen, and on a spy that call goes to the real method. - The real
get(0)runs and throwsIndexOutOfBoundsException. UsedoReturn("x").when(list).get(0).