Standalone mocks
Use apitest mocks in any test, such as a unit test of an HTTP client.
Mocks do not need an apitest test. EndStandalone installs a mock and returns a function that removes it, which makes apitest a convenient way to test HTTP client code:
// UsersClient is an HTTP client for the users service.
type UsersClient struct {
BaseURL string
}
func (c UsersClient) GetUser(id string) (User, error) {
var user User
err := getJSON(context.Background(), c.BaseURL+"/api/user/"+id, &user)
return user, err
}
func TestUsersClient_GetUser(t *testing.T) {
defer apitest.NewMock().
Get("http://users/api/user/1234").
RespondWith().
Status(http.StatusOK).
Body(`{"id": "1234", "name": "jon"}`).
EndStandalone()()
user, err := UsersClient{BaseURL: "http://users"}.GetUser("1234")
if err != nil {
t.Fatal(err)
}
if user.Name != "jon" {
t.Fatalf("unexpected user %+v", user)
}
}
EndStandalone() returns the reset function, so defer ...EndStandalone()() installs the mock now and removes it when the test returns.
Several mocks
EndStandalone accepts more mocks as arguments. NewStandaloneMocks reads more clearly when there are several:
reset := apitest.NewStandaloneMocks(
apitest.NewMock().
Get("http://users/api/user/1234").
RespondWith().
Status(http.StatusOK).
Body(`{"id": "1234", "name": "jon"}`).
End(),
apitest.NewMock().
Get("http://users/api/user/9999").
RespondWith().
Status(http.StatusNotFound).
End(),
).End()
defer reset()
client := UsersClient{BaseURL: "http://users"}
if _, err := client.GetUser("1234"); err != nil {
t.Fatal(err)
}
if _, err := client.GetUser("9999"); !errors.Is(err, errNotFound) {
t.Fatalf("expected not found, got %v", err)
}Custom clients and debugging
HttpClient replaces the transport of a specific client, and Debug prints each call and the reasons for any mismatch:
cli := &http.Client{Timeout: time.Second}
reset := apitest.NewStandaloneMocks(
apitest.NewMock().
Get("http://users/api/user/1234").
RespondWith().
Status(http.StatusOK).
End(),
).
HttpClient(cli).
Debug().
End()
defer reset()
res, err := cli.Get("http://users/api/user/1234")On a single mock, the equivalents are apitest.NewMock().HttpClient(cli) and apitest.NewMock().Debug().
Repeated calls
As in a test, a standalone mock answers one call by default. Times(n) makes it answer n calls, and AnyTimes() any number:
defer apitest.NewMock().
Get("http://users/api/user/1234").
RespondWith().
Status(http.StatusOK).
Body(`{"id": "1234", "name": "jon"}`).
Times(2).
EndStandalone()()
client := UsersClient{BaseURL: "http://users"}
for range 2 {
if _, err := client.GetUser("1234"); err != nil {
t.Fatal(err)
}
}Standalone mode does not check call counts, so a mock called fewer times than Times asks for does not fail the test. To require every call, run the code inside an apitest test, or count the calls with your own assertions.
Before v1.7.0
Earlier versions ignored Times on standalone mocks, so each one answered a single call unless AnyTimes was set.
As with mocks in a test, standalone mocks on the default transport affect the whole process, so do not run these tests in parallel unless each uses its own client.