httpmock
Easy mocking of http responses from external resources.
Install
Currently supports Go 1.13 to 1.21 and is regularly tested against tip.
v1
branch has to be used instead of master
.
In your go files, simply use:
import "github.com/jarcoal/httpmock"
Then next go mod tidy
or go test
invocation will automatically
populate your go.mod
with the latest httpmock release, now
.
Usage
Simple Example:
func TestFetchArticles(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))
...
httpmock.GetTotalCallCount()
info := httpmock.GetCallCountInfo()
info["GET https://api.mybiz.com/articles"]
info["GET https://api.mybiz.com/articles/id/12"]
info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`]
}
Advanced Example:
func TestFetchArticles(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
articles := make([]map[string]interface{}, 0)
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(200, articles)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
})
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`,
func(req *http.Request) (*http.Response, error) {
id := httpmock.MustGetSubmatchAsUint(req, 1)
return httpmock.NewJsonResponse(200, map[string]interface{}{
"id": id,
"name": "My Great Article",
})
})
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
func(req *http.Request) (*http.Response, error) {
article := make(map[string]interface{})
if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
return httpmock.NewStringResponse(400, ""), nil
}
articles = append(articles, article)
resp, err := httpmock.NewJsonResponse(200, article)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
})
httpmock.RegisterMatcherResponder("POST", "https://api.mybiz.com/articles",
httpmock.BodyContainsString(`"type":"toy"`),
httpmock.NewStringResponder(400, `{"reason":"Invalid article type"}`))
}
Algorithm
When GET http://example.tld/some/path?b=12&a=foo&a=bar
request is
caught, all standard responders are checked against the following URL
or paths, the first match stops the search:
http://example.tld/some/path?b=12&a=foo&a=bar
(original URL)http://example.tld/some/path?a=bar&a=foo&b=12
(sorted query params)http://example.tld/some/path
(without query params)/some/path?b=12&a=foo&a=bar
(original URL without scheme and host)/some/path?a=bar&a=foo&b=12
(same, but sorted query params)/some/path
(path only)
If no standard responder matched, the regexp responders are checked,
in the same order, the first match stops the search.
import (
"testing"
"github.com/jarcoal/httpmock"
"github.com/maxatome/go-testdeep/helpers/tdsuite"
"github.com/maxatome/go-testdeep/td"
)
type MySuite struct{}
func (s *MySuite) Setup(t *td.T) error {
httpmock.Activate()
return nil
}
func (s *MySuite) PostTest(t *td.T, testName string) error {
httpmock.Reset()
return nil
}
func (s *MySuite) Destroy(t *td.T) error {
httpmock.DeactivateAndReset()
return nil
}
func TestMySuite(t *testing.T) {
tdsuite.Run(t, &MySuite{})
}
func (s *MySuite) TestArticles(assert, require *td.T) {
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
}
import (
"github.com/jarcoal/httpmock"
)
var _ = BeforeSuite(func() {
httpmock.Activate()
})
var _ = BeforeEach(func() {
httpmock.Reset()
})
var _ = AfterSuite(func() {
httpmock.DeactivateAndReset()
})
import (
"github.com/jarcoal/httpmock"
)
var _ = Describe("Articles", func() {
It("returns a list of articles", func() {
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
})
})
import (
"github.com/jarcoal/httpmock"
"github.com/go-resty/resty"
)
var _ = BeforeSuite(func() {
httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
})
var _ = BeforeEach(func() {
httpmock.Reset()
})
var _ = AfterSuite(func() {
httpmock.DeactivateAndReset()
})
import (
"github.com/jarcoal/httpmock"
"github.com/go-resty/resty"
)
var _ = Describe("Articles", func() {
It("returns a list of articles", func() {
fixture := `{"status":{"message": "Your message", "code": 200}}`
responder := httpmock.NewStringResponder(200, fixture)
fakeUrl := "https://api.mybiz.com/articles.json"
httpmock.RegisterResponder("GET", fakeUrl, responder)
articleObject := &models.Article{}
_, err := resty.R().SetResult(articleObject).Get(fakeUrl)
})
})