There’s a wall every developer eventually hits when writing test code.
The moment you start wondering, “Is this a Mock or a Stub?”
They all seem like “fake objects,” but hearing “That’s a Stub, not a Mock” in a code review can make your mind go blank.
This article clearly explains the differences among the five test-double siblings—Dummy, Stub, Spy, Mock, and Fake—with examples.
Let’s start with the conclusion.
Test doubles are distinguished by what you verify. If it returns a value, it’s a Stub; if it records calls, it’s a Spy; if it checks whether calls match expectations, it’s a Mock; and if it behaves like the real thing, it’s a Fake.
Remember that one sentence and you’re halfway there.
What exactly is a test double?
Test Double comes from the movie term “stunt double.”
Like a stand-in who performs dangerous scenes instead of an actor, it refers collectively to fake objects that replace real ones.
Gerard Meszaros systematized this concept in 『xUnit Test Patterns』 (2007).
So Mock and Stub are subtypes of test doubles.
Why use them? Say you’re testing payment logic—you can’t call the real card-issuer API.
It’s slow, costs money, and a network outage can break the test.
That’s why we put a stand-in in place of the real thing.
Mock, Stub, Spy, and Fake: A One-Table Comparison
Explaining this in words can stay confusing, so let’s look at the table first.
| Type | Core role | What is verified |
|---|---|---|
| Dummy | Fills a slot | Nothing verified |
| Stub | Returns a predetermined value | State (result) |
| Spy | Records call history | State + call history |
| Mock | Checks whether calls match expectations | Behavior (calls) |
| Fake | Behaves like the real thing, lightly | State (result) |
There’s exactly one key dividing line here.
State verification or behavior verification.
Stub and Fake check whether “the result came out this way” (state verification).
Mock checks whether “the method was actually called” (behavior verification).
Spy sits in between, quietly recording the calls.
It clicks when you see it in code
Code is faster than words. Let’s use user notifications as an example. In Swift, it’s customary to create and use test doubles that adopt a protocol directly.
First, Stub. It’s a stand-in that returns only predetermined values.
// getNameFixed to always return “Hong Gil-dong”
final class UserRepositoryStub: UserRepository {
func getName(id: Int) -> String { "Hong Gil-dong" }
}
let service = GreetingService(repository: UserRepositoryStub())
// The test only checks whether the result (state) is correct
#expect(service.greet(id: 1) == "Hong Gil-dong")
Next is Mock. It verifies whether “this method was called.”
// Verify that the notification was actually sent (called)
final class NotificationSenderMock: NotificationSender {
var sendCallCount = 0
func send(_ message: String) { sendCallCount += 1 }
}
let mockSender = NotificationSenderMock()
let service = UserService(sender: mockSender)
service.notifyUser(id: 1)
// Behavior verification: sendexactly 1times called
#expect(mockSender.sendCallCount == 1)
Can you see the difference? Stub checks the return value (result), while Mock checks the number of calls.
This difference in perspective is the key to understanding test doubles.
When should you use Spy and Fake?
Spy wraps a real object, leaves its behavior intact, and quietly records only the call history.
Use it when “you want to keep the real logic but also know how many times it was called.”
In Swift, a common approach is to create a Spy class that delegates to the real implementation while recording call arguments and counts in properties.
Fake behaves similarly to the real thing but is a much lighter implementation.
Common examples include an in-memory store used instead of a real DB or a fake repository built with Dictionary.
It behaves like the real thing but isn’t sufficient for production—think of it as an implementation strictly for tests.
To summarize, they break down like this.
- I only need a value → Stub
- I need to verify whether it was called → Mock
- I need both real behavior and call recording → Spy
- I need a lightweight implementation that behaves like the real thing → Fake
Frequently asked questions
Q. Can Mock and Stub simply be mixed in practice?
In practice, one custom test-double class often serves both as a value-returning Stub and a call-recording Mock, so the boundary is blurry. Still, distinguishing whether the purpose is “to provide a value” or “to verify a call” makes the test’s intent clear.
Q. I heard using too many Mocks is bad.
Overusing behavior verification makes tests break in droves whenever the internal implementation changes slightly. So, when possible, favor state verification (Stub and Fake), and use Mock only for essential collaborations.
Instead of memorizing all five types, just ask yourself, “Do I need a value right now, or am I verifying a call?”
That one question naturally tells you which stand-in to create.
If you get confused again while writing tests today, come back to this article. I hope your tests become much more robust!

