# Test doubles: stubs, spies, mocks and fakes

> The five kinds of stand-in for a real dependency in tests, what each is for, and why outcome-based fakes usually beat interaction mocks.

Concept · Testing practice · https://levelcode.org/concepts/test-doubles

A **test double** stands in for a real dependency — a database, a payment API, the clock — so a unit test can run fast and deterministically. Gerard Meszaros named five kinds, and the names are worth using precisely:

| Double | What it does | Example |
| --- | --- | --- |
| **Dummy** | Fills a parameter that's never used | `nil` logger, empty struct |
| **Stub** | Returns canned answers | a clock that always returns 09:00; a store that always returns "not found" |
| **Spy** | A stub that also records calls | a mailer that appends each sent message to a slice |
| **Mock** | Pre-programmed with expected calls; fails the test if they don't happen | "expect `Charge(order 42, 15000)` exactly once" |
| **Fake** | A working, simplified implementation | an in-memory repository backed by a map |

## Writing them in Go

With small, consumer-defined interfaces, hand-written doubles take a few lines:

```go
type fakeOrders struct{ saved map[string]Order }

func (f *fakeOrders) Save(_ context.Context, o Order) error {
	f.saved[o.ID] = o
	return nil
}

type stubClock struct{ now time.Time }

func (c stubClock) Now() time.Time { return c.now }
```

Mocking libraries such as `gomock` or `testify/mock` generate mocks from interfaces; in TypeScript, Vitest and Jest provide `vi.fn()` / `jest.fn()` spies and module mocking.

## Outcomes over interactions

Two styles of assertion:

- **State/outcome-based:** after the action, check results — "the order is saved with status Paid", "one receipt was sent". Uses fakes, stubs and spies.
- **Interaction-based:** check that specific calls happened in a specific way — "`Save` was called, then `Publish`, with these arguments". Uses mocks.

Interaction tests couple to *how* the code works, so they break on harmless refactors, and they can pass while the real behaviour is wrong. Prefer outcome-based tests, and reserve mocks for interactions that *are* the behaviour — "the payment provider must be called exactly once", "no email is sent for opted-out users".

## Guidelines

- Double what's **slow, non-deterministic or external**; use the real thing for pure in-process logic.
- Don't mock types you don't own — wrap a vendor SDK in your own [adapter](https://levelcode.org/concepts/ports-and-adapters) and double that.
- Keep fakes honest by running a shared **contract test** against both the fake and the real implementation ([Liskov substitution](https://levelcode.org/concepts/liskov-substitution) in practice).
- Prove the real wiring with a few integration tests; see the [test pyramid](https://levelcode.org/concepts/test-pyramid).
