Last time I covered testing in Go I mentioned a package used for testing equality called github.com/google/go-cmp, this time I will share with you a way to mock HTTP traffic using the package github.com/h2non/gock
.
Mocking HTTP traffic
There's a simple CLI tool I built for requesting OpenWeather information using their API, please refer to the final repository for actually running the full examples.
This CLI allows getting specific details by passing in a zip code as well as the App ID OpenWeather token, outputting something like:
$ go run main.go -appID <TOKEN> -zip 90210
{Weather:[{Description:broken clouds}] Main:{Temperature:53.24 FeelsLike:48.63}}
In practice the important part of this tool that is relevant to this post is the unexported function that does the request called requestWeather
:
func requestWeather(ctx context.Context, client *http.Client, appID, zip, units string) (Result, error) {
req, err := http.NewRequestWithContext(ctx,
http.MethodGet,
"https://api.openweathermap.org/data/2.5/weather",
nil)
if err != nil {
return Result{}, err // Extra Case, not http-request-based but it is covered in the tests.
}
url := req.URL.Query()
url.Add("zip", zip)
url.Add("appid", appID)
url.Add("units", units)
req.URL.RawQuery = url.Encode()
res, err := client.Do(req)
if err != nil {
return Result{}, err // Case 2: "Third Party Error: when doing request."
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
var errJSON struct {
Message string `json:"message"`
}
if err := json.NewDecoder(res.Body).Decode(&errJSON); err != nil {
return Result{}, err // Case 4: "Third Party Error: not a 200 status code, can't unmarshal error response."
}
return Result{}, fmt.Errorf(errJSON.Message) // Case 3: "Third Party Error: not a 200 status code, returns error in response."
}
var result Result
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return Result{}, err // Case 5: "Third Party Error: 200 status code but is not valid JSON."
}
return result, nil // Case 1: "No errors: represented by a 200 status code."
}
If we use the number of return
statements in this function we can determine that at least five subtests are needed to cover all the different paths, and more specifically the need to implement five cases involving HTTP requests:
- Case 1: No errors: represented by a 200 status code.
- Case 2: Third Party Error: when doing request.
- Case 3: Third Party Error: not a 200 status code, returns error in response.
- Case 4: Third Party Error: not a 200 status code, can't unmarshal error response.
- Case 5: Third Party Error: 200 status code but is not valid JSON.
Structuring tests
The way the tests are implemented in Test_requestWeather is really intentional:
- Clean up section,
- Disable real networking (
gock.DisableNetworking()
), - Define types and subtests, and
- Run subtests.
That implementation does not call t.Paralell()
because github.com/h2non/gock
is not goroutine safe; similarly the subtest structure is influenced by that limitation where each subtest defines a setup func()
to explicitly call h2non/gock
to mock concrete HTTP traffic, for example in Case 1:
func() {
gock.New("https://api.openweathermap.org").
MatchParams(map[string]string{
"zip": "90210",
"appid": "appID",
"units": "metric",
}).
Get("/data/2.5/weather").
Reply(http.StatusOK).
File(path.Join("fixtures", "200.json"))
},
By doing this we have to opportunity to test all the different paths because in the end we use different arguments for each call preventing previous mocked traffic to conflict with each other.
h2non/gock API
h2non/gock
's API is straightforward, it defines a Request
type used to match the request to mock, this allows defining concrete configuration values like arguments, body and headers; then a Response
type is used to return values back to the client, similarly there are methods to indicate concrete headers, status code and the body content, like maps or files.
Final thoughts
github.com/h2non/gock
is one of the multiple ways to mock HTTP traffic, its simple yet powerful API allows anyone to quickly learn it and start using it because it covers most of the use cases regarding testing HTTP traffic.
If my project requires integrating with third party HTTP-based APIs, I can always rely on using github.com/h2non/gock
for mocking purposes. Highly recommended.
Top comments (0)