apitest
Mocks

How matching works

The order mocks are tried in, the rules for each part of the request, and how to debug a mismatch.

Order

For each outbound request, mocks are tried in the order they were passed to Mocks. The first mock whose matchers all pass answers the request. By default a mock answers one request and is then skipped, so mocks for the same URL answer successive calls in turn:

// the first call fails, the next two succeed
unavailable := apitest.NewMock().
	Get("http://flaky/api/status").
	RespondWith().
	Status(http.StatusServiceUnavailable).
	End()

available := apitest.NewMock().
	Get("http://flaky/api/status").
	RespondWith().
	Status(http.StatusOK).
	Times(2).
	End()

apitest.New().
	Mocks(unavailable, available).
	Handler(handler).
	Get("/").
	Expect(t).
	Body("503,200,200").
	End()

Call counts explains Times and AnyTimes.

The rules

PartRule
PathEqual to the mock path, or matched by it as a regular expression. Ignored when the mock has no URL.
HostEqual to the mock host. Ignored when the mock URL has no host.
SchemeEqual to the mock scheme. Ignored when the mock URL has no scheme.
MethodEqual to the mock method.
Headers, query parameters, form dataEach value is a regular expression matched against the received values.
BodyEqual to the mock body as a string, or equivalent JSON. BodyRegexp matches a regular expression.
Cookies, basic authEqual to the mock's values.
Custom matchersReturn nil.

Every matcher must pass. A matcher whose part of the mock was not set, such as headers on a mock without Header, always passes.

Paths are regular expressions

The mock path is a regular expression, and it is not anchored. Get("http://users/api/user/\\d+") matches /api/user/1234, and also /api/user/1234/avatar:

// matches /api/user/1234 and /api/user/1234/avatar: the expression is not anchored
anyUserPath := apitest.NewMock().
	Get(`http://users/api/user/\d+`).
	RespondWith().
	Status(http.StatusOK).
	End()

// $ anchors the end, so /api/user/1234/avatar no longer matches
userOnly := apitest.NewMock().
	Get(`http://users/api/user/\d+$`).
	RespondWith().
	Status(http.StatusOK).
	End()

// leave out the scheme and host to anchor the start with ^ as well
anchored := apitest.NewMock().
	Get(`^/api/user/\d+$`).
	RespondWith().
	Status(http.StatusOK).
	End()

Regular expression characters in a path, such as ., + and ?, also need escaping when you want them matched literally.

Debugging a mismatch

When no mock matches, the client receives an error, and the handler usually responds with an error status. Enable Debug to see why each mock was rejected. In this run the users mock has a typo in its path, and the preferences mock expects a different token:

Debug output
----------> inbound http request
GET /user/1234 HTTP/1.1
Host: sut


failed to match mocks. Errors: received request did not match any mocks

Mock 1 mismatches:
• received path /api/user/1234 did not match mock path /api/user/12345

Mock 2 mismatches:
• received path /api/user/1234 did not match mock path /api/preferences/1234
• received host users did not match mock host preferences
• not all of received headers map[Authorization:[Bearer s3cr3t]] matched expected mock headers map[Authorization:[Bearer token]]

----------> request to mock
GET /api/user/1234 HTTP/1.1
Host: users
User-Agent: Go-http-client/1.1
Authorization: Bearer s3cr3t
Accept-Encoding: gzip

<---------- response from mock

<---------- final response
HTTP/1.1 502 Bad Gateway
Content-Type: application/json

{"error":"upstream error"}

Mocks that have already answered their calls are skipped, so they do not appear in the list.

In a sequence diagram

A sequence diagram shows the same failure at a glance. A call that matched no mock gets no response, so the report marks its arrow with a ✕ and labels the message no response:

// the mock is for user 9999, so the call for user 1234 matches no mock
wrongUser := apitest.NewMock().
	Get("http://users/api/user/9999").
	RespondWith().
	Status(http.StatusOK).
	Body(`{"id": "9999", "name": "ola"}`).
	End()

apitest.New("reports a call that matched no mock").
	Report(apitest.SequenceDiagram()).
	Mocks(wrongUser).
	Handler(newUserAPI()).
	Get("/user/1234").
	Expect(t).
	Status(http.StatusBadGateway).
	End()
An apitest report where the call from sut to users is marked as having no response, and the test responds 502 Bad Gateway
Open the live report

ObserveMocks reports every mocked call programmatically, including the ones that did not match.

On this page