# Test doubles

> Replace the App's services in a test with fakes of your own, fake its events, and keep its tests fast and independent.

This guide covers test doubles: stand-ins for services an App uses, in a test. You will learn how to:

- Replace one of the App's services for a test with `tinkatest.Fake`
- Write a fake behind a small interface, and check it against the real service
- Record the App's events instead of delivering them
- Know when a double makes a test worse

## Overview

A test of a handler that sends an email shouldn't send one. It replaces the mailer with a **test double**, a stand-in the test controls, and checks what the handler asked of it. The common kinds:

| Double | Does | Use it for |
|---|---|---|
| Fake | works, simply: an in-memory store, a mailbox that keeps what it was sent | services with state, the usual choice |
| Stub | returns one canned answer, or an error | one path, such as a payment that's declined |
| Spy | records how it was called | when the call itself is the behavior, such as an event announced to other modules |

Prefer fakes. A test that checks what a fake ended up holding, such as "Ada has one email", survives a change to how the code sends it; a test that checks each call breaks on any refactor.

## Replace a service

`tinkatest.Fake[T]` makes every service of type `T` the App resolves from its [container](../concepts/dependency-injection.md) be the value you give, until the test ends. Call it before `tinkatest.New` starts the App: services built when the App boots keep what they were given, so a later swap wouldn't reach them, and `Fake` fails the test to say so.

```go title="tests/functional/signup_test.go"
func TestSignupSendsAWelcome(t *testing.T) {
	t.Parallel()
	app := tests.App(t)
	mailer := &tests.Mailer{}
	tinkatest.Fake[mail.Mailer](t, app, mailer)
	c := tinkatest.New(t, app)

	c.Post("/signup").Form(url.Values{"email": {"ada@example.com"}}).Send(t).AssertRedirect("/welcome")

	if got := mailer.To("ada@example.com"); len(got) != 1 || got[0].Subject != "Welcome" {
		t.Errorf("emails to Ada = %+v, want one welcome", got)
	}
}
```

Each test builds its own App, so a fake never reaches another test, even in parallel.

## Write a fake

Fakes swap best behind a small interface that the code using the service declares, with only the methods it calls. The App registers the real service under that interface, and the test's fake takes its place:

```go title="app/mail/mail.go"
package mail

import "context"

// A Message is an email to send.
type Message struct {
	To, Subject, Body string
}

// A Mailer sends email.
type Mailer interface {
	Send(ctx context.Context, m Message) error
}
```

```go title="tests/fakes.go"
package tests

import (
	"context"
	"sync"

	"blog/app/mail"
)

// Mailer keeps what it's sent, for tests to check.
type Mailer struct {
	mu   sync.Mutex
	sent []mail.Message
}

func (f *Mailer) Send(ctx context.Context, m mail.Message) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.sent = append(f.sent, m)
	return nil
}

// To returns the messages sent to address.
func (f *Mailer) To(address string) []mail.Message {
	f.mu.Lock()
	defer f.mu.Unlock()
	var out []mail.Message
	for _, m := range f.sent {
		if m.To == address {
			out = append(out, m)
		}
	}
	return out
}
```

The lock matters: the App handles requests on goroutines of its own, and `go test -race` reports a fake that isn't safe for them.

A fake is only useful while it behaves like the real service. Write the expectations both must meet as one test function, and run it against each: the fake in every run, and the real service where it can run, such as against a test inbox.

## Fake events

`tinkatest.Events` records the App's [events](../digging-deeper/emitter.md) instead of delivering them, until the test ends:

```go
	app := tests.App(t)
	sent := tinkatest.Events(t, app)
	c := tinkatest.New(t, app)

	c.Post("/signup").Form(form).Send(t).AssertRedirect("/welcome")
	sent.AssertEmitted[UserRegistered](t, func(e UserRegistered) bool { return e.Email == "ada@example.com" })
```

Assert an event when the event is the contract, such as one another part of the App reacts to. When what matters is the effect, such as the welcome email, let the listener run and check the effect instead.

## Hash passwords quickly

Hashing a password for real takes about 130 milliseconds, on purpose. The skeleton's `tests.App` replaces the App's `*password.Hasher` with `password.Fake()`, which takes under a millisecond, so tests that log in stay fast. Hashes it makes only verify in tests.

## When a double makes a test worse

- **More than about three doubles in one test** usually means the code under test does too much. Move the work into a type of its own, and test it directly.
- **A double that copies half of another service's behavior** will drift from it. Check it against the real one, as above, or use the real one.
- **Don't fake the database driver**, or assert on the SQL a query sends: test code that reads and writes data against a real database.
- **A test that breaks when you refactor without changing behavior** checks how the code works rather than what it does. Assert on outcomes.

See also: [HTTP tests](http-tests.md), [Dependency injection](../concepts/dependency-injection.md), [Emitter](../digging-deeper/emitter.md)
