> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gocopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

Copper applications are plain Go, so testing is plain `go test`. Copper helps in two ways: every package's dependencies are explicit, so test doubles drop in naturally, and most Copper packages ship a `<pkg>test` sibling with ready-made test helpers.

### Testing Handlers

Since handlers are exported methods on a router you construct yourself, you may unit-test them directly with `httptest`, without starting a server:

```go pkg/rockets/router_test.go theme={null}
func TestHandleLaunchRocket(t *testing.T) {
	t.Parallel()

	router := rockets.NewRouter(rockets.NewRouterParams{
		Rockets: &rocketstest.MockSvc{},
		RW:      chttptest.NewJSONReaderWriter(t),
		Logger:  clogger.NewNoop(),
	})

	req := httptest.NewRequest(http.MethodPost, "/api/rockets/falcon/launch", nil)
	req = mux.SetURLVars(req, map[string]string{"id": "falcon"})
	resp := httptest.NewRecorder()

	router.HandleLaunchRocket(resp, req)

	require.Equal(t, http.StatusOK, resp.Code)
}
```

<Note>
  `mux.SetURLVars` (from `github.com/gorilla/mux`, Copper's router) injects path parameters when you call a handler directly, since no routing happened. You may skip it for routes without `{params}`.
</Note>

If you would like to exercise routing, middleware, and handlers together, build a real handler with `chttp.NewHandler` and serve it with `httptest.NewServer`.

### Test Helpers

Most helpers live in a `<pkg>test` sibling package; a few (like `clogger`'s and `cmetrics`'s) ship in the package itself:

| Helper                                                        | Use                                                            |
| ------------------------------------------------------------- | -------------------------------------------------------------- |
| `clogger.NewNoop()`                                           | A logger that discards everything; the workhorse of test setup |
| `clogger.NewRecorder(&logs)`                                  | Records logs into a slice so you can assert on them            |
| `chttptest.NewJSONReaderWriter(t)` / `NewHTMLReaderWriter(t)` | Ready-made reader/writers for router tests                     |
| `chttptest.NewRouter(routes)` / `PingRoutes(t, routes)`       | Wrap ad-hoc routes; assert every route responds                |
| `cconfigtest.SetupDirWithConfigs(t, files)`                   | Temp config dir from a map of file contents                    |
| `clifecycletest.New()`                                        | A lifecycle for tests, no signal handling                      |
| `cmetrics.NewNoopMetrics()`                                   | Metrics that go nowhere                                        |

To assert on your application's logs, record them with `clogger.NewRecorder`:

```go theme={null}
var logs []clogger.RecordedLog

svc := rockets.NewSvc(rockets.NewSvcParams{
	Logger: clogger.NewRecorder(&logs),
})

// ...

require.Len(t, logs, 1)
require.Equal(t, clogger.LevelError, logs[0].Level)
```

### Mocking Your Own Packages

You may follow the same convention for your own services. Define an interface in the package, and place a hand-written mock in a `<pkg>test` sibling so that other packages can use it:

```go pkg/rockets/rocketstest/svc.go theme={null}
package rocketstest

var _ rockets.Svc = (*MockSvc)(nil)

type MockSvc struct {
	LaunchErr   error
	LaunchCalls []string
}

func (m *MockSvc) Launch(ctx context.Context, id string) (*rockets.Launch, error) {
	m.LaunchCalls = append(m.LaunchCalls, id)

	if m.LaunchErr != nil {
		return nil, m.LaunchErr
	}

	return &rockets.Launch{RocketID: id}, nil
}
```

The `var _ rockets.Svc = (*MockSvc)(nil)` line asks the compiler to verify that the mock stays in sync with the interface, while exported fields like `LaunchErr` allow each test to force the failure it needs.
