Mocking HTTP calls
Answer the outbound HTTP calls your handler makes, so tests are fast and repeatable.
Most services call other services. Calling the real ones from a test makes it slow, flaky and dependent on data you do not control. apitest mocks answer those calls instead, so each test decides exactly what its dependencies return.
getUser := apitest.NewMock().
Get("http://users/api/user/1234").
RespondWith().
Status(http.StatusOK).
Body(`{"id": "1234", "name": "jon"}`).
End()
getPreferences := apitest.NewMock().
Get("http://preferences/api/preferences/1234").
RespondWith().
Status(http.StatusOK).
JSON(map[string]any{"is_contactable": false}).
End()
apitest.New().
Mocks(getUser, getPreferences).
Handler(newUserAPI()).
Get("/user/1234").
Expect(t).
Status(http.StatusOK).
Body(`{"id": "1234", "name": "jon", "is_contactable": false}`).
End()A mock describes the request to match and the response to return. When the handler under test calls http://users/api/user/1234, the first mock answers it. Nothing leaves the process.
How mocks work
Go's HTTP clients send requests through a http.RoundTripper, called a transport. When a test has mocks, apitest swaps the transport for its own for the duration of the test:
- Before the request is sent,
http.DefaultTransportis replaced. WithHttpClient, the transport of that client is replaced instead. - Each outbound request is compared with the mocks, in order. The first mock whose matchers all pass builds the response.
- A request that matches no mock fails with an error that explains why each mock was rejected.
- When the test ends, the original transport is restored.
http.Get, http.Post, http.DefaultClient and any http.Client without its own Transport use http.DefaultTransport, so they are mocked with no changes to your code.
Unmatched requests are not sent
While mocks are active, every request through the replaced transport must match a mock. There is no fall through to the real network.
Testing error paths
Mocks make failure scenarios easy to set up, such as a dependency returning 404:
userNotFound := apitest.NewMock().
Get("http://users/api/user/9999").
RespondWith().
Status(http.StatusNotFound).
End()
apitest.New().
Mocks(userNotFound).
Handler(newUserAPI()).
Get("/user/9999").
Expect(t).
Status(http.StatusNotFound).
Body(`{"error": "user not found"}`).
End()Failures and latency covers timeouts and slow responses.
Mocks complement integration tests
Mocks keep the feedback loop short while you develop, but they only know what you tell them. Keep some tests that exercise the real dependencies, for example end-to-end tests against a staging environment, to catch changes in their behaviour.
In this section
Defining mocks
The request and response builders.
How matching works
Order, paths, hosts and debugging mismatches.
Request matchers
Headers, query, body, form, cookies and auth.
Custom matchers
Your own matching logic, and JSONPath matchers.
Responses
Status, headers, cookies and bodies.
Call counts
Times, AnyTimes and unmatched mocks.
Failures and latency
Timeouts and slow responses.
HTTP clients
Custom clients and parallel tests.
Observing mocks
Inspect every mocked call.
Standalone mocks
Mock HTTP clients outside an apitest test.