Testing & Code Quality

Test Doubles Explained: Mock, Stub, Spy, and Fake (Examples & Table)

There’s a wall every developer eventually hits when writing test code.

4 min read
Cover image for Test Doubles Explained: Mock, Stub, Spy, and Fake (Examples & Table)

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.

Swift Testing unit-test code screen verifying Mock and Stub classes with #expect
In the end, whether you inspect the return value or count calls becomes obvious in code.

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.

  1. I only need a value → Stub
  2. I need to verify whether it was called → Mock
  3. I need both real behavior and call recording → Spy
  4. I need a lightweight implementation that behaves like the real thing → Fake
When unsure, use this flowchart and ask only: value or call?
When unsure, use this flowchart and ask only: value or call?

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!