From c43cb5160c518aa2843dd4a9f9c7a08050f772cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Fri, 26 Aug 2022 12:23:06 +0200 Subject: [PATCH 1/2] ci: switch to go1.19, golangci-lint v1.49.0, go-testdeep v1.12.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime Soulé --- .github/workflows/ci.yml | 6 +- README.md | 2 +- doc.go | 138 ++++++------ file.go | 21 +- go.mod | 2 +- go.sum | 4 +- response.go | 245 +++++++++++---------- response_test.go | 2 +- transport.go | 457 +++++++++++++++++++++------------------ util_test.go | 2 +- 10 files changed, 469 insertions(+), 410 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae36467..4f5bf28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,10 +10,10 @@ jobs: test: strategy: matrix: - go-version: [1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, tip] + go-version: [1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, tip] full-tests: [false] include: - - go-version: 1.18.x + - go-version: 1.19.x full-tests: true runs-on: ubuntu-latest @@ -31,7 +31,7 @@ jobs: if: matrix.full-tests run: | curl -sL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | - sh -s -- -b $HOME/go/bin v1.45.2 + sh -s -- -b $HOME/go/bin v1.49.0 $HOME/go/bin/golangci-lint run --max-issues-per-linter 0 \ --max-same-issues 0 \ -E bidichk \ diff --git a/README.md b/README.md index 64e12f3..9425f1e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Easy mocking of http responses from external resources. ## Install -Currently supports Go 1.9 - 1.18. +Currently supports Go 1.9 - 1.19. `v1` branch has to be used instead of `master`. diff --git a/doc.go b/doc.go index 89d54bb..ea3491c 100644 --- a/doc.go +++ b/doc.go @@ -2,80 +2,82 @@ Package httpmock provides tools for mocking HTTP responses. Simple Example: - func TestFetchArticles(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - // Exact URL match - httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles", - httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`)) + func TestFetchArticles(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() - // Regexp match (could use httpmock.RegisterRegexpResponder instead) - httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`, - httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`)) + // Exact URL match + httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles", + httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`)) - // do stuff that makes a request to articles + // Regexp match (could use httpmock.RegisterRegexpResponder instead) + httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`, + httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`)) - // get count info - httpmock.GetTotalCallCount() + // do stuff that makes a request to articles - // get the amount of calls for the registered responder - info := httpmock.GetCallCountInfo() - info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles - info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12 - info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/ - } + // get count info + httpmock.GetTotalCallCount() + + // get the amount of calls for the registered responder + info := httpmock.GetCallCountInfo() + info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles + info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12 + info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/ + } Advanced Example: - func TestFetchArticles(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - // our database of articles - articles := make([]map[string]any, 0) - - // mock to list out the articles - 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 - }, - ) - - // return an article related to the request with the help of regexp submatch (\d+) - httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`, - func(req *http.Request) (*http.Response, error) { - // Get ID from request - id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch - return httpmock.NewJsonResponse(200, map[string]any{ - "id": id, - "name": "My Great Article", - }) - }, - ) - - // mock to add a new article - httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles", - func(req *http.Request) (*http.Response, error) { - article := make(map[string]any) - 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 - }, - ) - - // do stuff that adds and checks articles - } + + func TestFetchArticles(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + // our database of articles + articles := make([]map[string]any, 0) + + // mock to list out the articles + 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 + }, + ) + + // return an article related to the request with the help of regexp submatch (\d+) + httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`, + func(req *http.Request) (*http.Response, error) { + // Get ID from request + id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch + return httpmock.NewJsonResponse(200, map[string]any{ + "id": id, + "name": "My Great Article", + }) + }, + ) + + // mock to add a new article + httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles", + func(req *http.Request) (*http.Response, error) { + article := make(map[string]any) + 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 + }, + ) + + // do stuff that adds and checks articles + } */ package httpmock diff --git a/file.go b/file.go index a363ed6..90d3964 100644 --- a/file.go +++ b/file.go @@ -2,28 +2,31 @@ package httpmock import ( "fmt" - "io/ioutil" + "io/ioutil" //nolint: staticcheck ) // File is a file name. The contents of this file is loaded on demand // by the following methods. // // Note that: -// file := httpmock.File("file.txt") -// fmt.Printf("file: %s\n", file) +// +// file := httpmock.File("file.txt") +// fmt.Printf("file: %s\n", file) // // prints the content of file "file.txt" as String() method is used. // // To print the file name, and not its content, simply do: -// file := httpmock.File("file.txt") -// fmt.Printf("file: %s\n", string(file)) +// +// file := httpmock.File("file.txt") +// fmt.Printf("file: %s\n", string(file)) type File string // MarshalJSON implements json.Marshaler. // // Useful to be used in conjunction with NewJsonResponse() or // NewJsonResponder() as in: -// httpmock.NewJsonResponder(200, httpmock.File("body.json")) +// +// httpmock.NewJsonResponder(200, httpmock.File("body.json")) func (f File) MarshalJSON() ([]byte, error) { return f.bytes() } @@ -37,7 +40,8 @@ func (f File) bytes() ([]byte, error) { // // Useful to be used in conjunction with NewBytesResponse() or // NewBytesResponder() as in: -// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) +// +// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) func (f File) Bytes() []byte { b, err := f.bytes() if err != nil { @@ -51,7 +55,8 @@ func (f File) Bytes() []byte { // // Useful to be used in conjunction with NewStringResponse() or // NewStringResponder() as in: -// httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) +// +// httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) func (f File) String() string { return string(f.Bytes()) } diff --git a/go.mod b/go.mod index 6d1f7b8..aa97be5 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module github.com/jarcoal/httpmock go 1.18 -require github.com/maxatome/go-testdeep v1.11.0 +require github.com/maxatome/go-testdeep v1.12.0 require github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index cedaf19..34d85ef 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/maxatome/go-testdeep v1.11.0 h1:Tgh5efyCYyJFGUYiT0qxBSIDeXw0F5zSoatlou685kk= -github.com/maxatome/go-testdeep v1.11.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= diff --git a/response.go b/response.go index 38895c5..45db89e 100644 --- a/response.go +++ b/response.go @@ -59,18 +59,19 @@ func (r Responder) times(name string, n int, fn ...func(...any)) Responder { // passed and non-nil, it acts as the fn parameter of // NewNotFoundResponder, allowing to dump the stack trace to localize // the origin of the call. -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// // This responder is callable 3 times, then an error is returned and -// // the stacktrace of the call logged using t.Log() -// httpmock.RegisterResponder("GET", "/foo/bar", -// httpmock.NewStringResponder(200, "{}").Times(3, t.Log), -// ) +// +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // This responder is callable 3 times, then an error is returned and +// // the stacktrace of the call logged using t.Log() +// httpmock.RegisterResponder("GET", "/foo/bar", +// httpmock.NewStringResponder(200, "{}").Times(3, t.Log), +// ) func (r Responder) Times(n int, fn ...func(...any)) Responder { return r.times("Times", n, fn...) } @@ -80,18 +81,19 @@ func (r Responder) Times(n int, fn ...func(...any)) Responder { // and non-nil, it acts as the fn parameter of NewNotFoundResponder, // allowing to dump the stack trace to localize the origin of the // call. -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// // This responder is callable only once, then an error is returned and -// // the stacktrace of the call logged using t.Log() -// httpmock.RegisterResponder("GET", "/foo/bar", -// httpmock.NewStringResponder(200, "{}").Once(t.Log), -// ) +// +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // This responder is callable only once, then an error is returned and +// // the stacktrace of the call logged using t.Log() +// httpmock.RegisterResponder("GET", "/foo/bar", +// httpmock.NewStringResponder(200, "{}").Once(t.Log), +// ) func (r Responder) Once(fn ...func(...any)) Responder { return r.times("Once", 1, fn...) } @@ -100,16 +102,17 @@ func (r Responder) Once(fn ...func(...any)) Responder { // of the original Responder using fn. It can be used in conjunction // with the testing package as in the example below with the help of // (*testing.T).Log method: -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// httpmock.RegisterResponder("GET", "/foo/bar", -// httpmock.NewStringResponder(200, "{}").Trace(t.Log), -// ) +// +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// httpmock.RegisterResponder("GET", "/foo/bar", +// httpmock.NewStringResponder(200, "{}").Trace(t.Log), +// ) func (r Responder) Trace(fn func(...any)) Responder { return func(req *http.Request) (*http.Response, error) { resp, err := r(req) @@ -122,17 +125,18 @@ func (r Responder) Trace(fn func(...any)) Responder { // Delay returns a new Responder that calls the original r Responder // after a delay of d. -// import ( -// "testing" -// "time" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// httpmock.RegisterResponder("GET", "/foo/bar", -// httpmock.NewStringResponder(200, "{}").Delay(100*time.Millisecond), -// ) +// +// import ( +// "testing" +// "time" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// httpmock.RegisterResponder("GET", "/foo/bar", +// httpmock.NewStringResponder(200, "{}").Delay(100*time.Millisecond), +// ) func (r Responder) Delay(d time.Duration) Responder { return func(req *http.Request) (*http.Response, error) { time.Sleep(d) @@ -152,23 +156,25 @@ func (r Responder) similar(other Responder) bool { // Then returns a new Responder that calls r on first invocation, then // next on following ones, except when Then is chained, in this case // next is called only once: -// A := httpmock.NewStringResponder(200, "A") -// B := httpmock.NewStringResponder(200, "B") -// C := httpmock.NewStringResponder(200, "C") // -// httpmock.RegisterResponder("GET", "/pipo", A.Then(B).Then(C)) +// A := httpmock.NewStringResponder(200, "A") +// B := httpmock.NewStringResponder(200, "B") +// C := httpmock.NewStringResponder(200, "C") +// +// httpmock.RegisterResponder("GET", "/pipo", A.Then(B).Then(C)) // -// http.Get("http://foo.bar/pipo") // A is called -// http.Get("http://foo.bar/pipo") // B is called -// http.Get("http://foo.bar/pipo") // C is called -// http.Get("http://foo.bar/pipo") // C is called, and so on +// http.Get("http://foo.bar/pipo") // A is called +// http.Get("http://foo.bar/pipo") // B is called +// http.Get("http://foo.bar/pipo") // C is called +// http.Get("http://foo.bar/pipo") // C is called, and so on // // A panic occurs if next is the result of another Then call (because // allowing it could cause inextricable problems at runtime). Then // calls can be chained, but cannot call each other by // parameter. Example: -// A.Then(B).Then(C) // is OK -// A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call +// +// A.Then(B).Then(C) // is OK +// A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call func (r Responder) Then(next Responder) (x Responder) { var done int var mu sync.Mutex @@ -254,24 +260,25 @@ func ResponderFromResponse(resp *http.Response) Responder { // and non-nil, it acts as the fn parameter of NewNotFoundResponder, // allowing to dump the stack trace to localize the origin of the // call. -// import ( -// "github.com/jarcoal/httpmock" -// "testing" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// // This responder is callable only once, then an error is returned and -// // the stacktrace of the call logged using t.Log() -// httpmock.RegisterResponder("GET", "/foo/bar", -// httpmock.ResponderFromMultipleResponses( -// []*http.Response{ -// httpmock.NewStringResponse(200, `{"name":"bar"}`), -// httpmock.NewStringResponse(404, `{"mesg":"Not found"}`), -// }, -// t.Log), -// ) -// } +// +// import ( +// "github.com/jarcoal/httpmock" +// "testing" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // This responder is callable only once, then an error is returned and +// // the stacktrace of the call logged using t.Log() +// httpmock.RegisterResponder("GET", "/foo/bar", +// httpmock.ResponderFromMultipleResponses( +// []*http.Response{ +// httpmock.NewStringResponse(200, `{"name":"bar"}`), +// httpmock.NewStringResponse(404, `{"mesg":"Not found"}`), +// }, +// t.Log), +// ) +// } func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...any)) Responder { responseIndex := 0 mutex := sync.Mutex{} @@ -321,24 +328,26 @@ func NewErrorResponder(err error) Responder { // mocked. // // Example of use: -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// // Calls testing.Fatal with the name of Responder-less route and -// // the stack trace of the call. -// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) +// +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // Calls testing.Fatal with the name of Responder-less route and +// // the stack trace of the call. +// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) // // Will abort the current test and print something like: -// transport_test.go:735: Called from net/http.Get() -// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 -// github.com/jarcoal/httpmock.TestCheckStackTracer() -// at /go/src/testing/testing.go:865 -// testing.tRunner() -// at /go/src/runtime/asm_amd64.s:1337 +// +// transport_test.go:735: Called from net/http.Get() +// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 +// github.com/jarcoal/httpmock.TestCheckStackTracer() +// at /go/src/testing/testing.go:865 +// testing.tRunner() +// at /go/src/runtime/asm_amd64.s:1337 func NewNotFoundResponder(fn func(...any)) Responder { return func(req *http.Request) (*http.Response, error) { var extra string @@ -357,7 +366,8 @@ func NewNotFoundResponder(fn func(...any)) Responder { // the given string. Also accepts an http status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewStringResponse(200, httpmock.File("body.txt").String()) +// +// httpmock.NewStringResponse(200, httpmock.File("body.txt").String()) func NewStringResponse(status int, body string) *http.Response { return &http.Response{ Status: strconv.Itoa(status), @@ -371,7 +381,8 @@ func NewStringResponse(status int, body string) *http.Response { // NewStringResponder creates a Responder from a given body (as a string) and status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) +// +// httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) func NewStringResponder(status int, body string) Responder { return ResponderFromResponse(NewStringResponse(status, body)) } @@ -380,7 +391,8 @@ func NewStringResponder(status int, body string) Responder { // given bytes. Also accepts an http status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewBytesResponse(200, httpmock.File("body.raw").Bytes()) +// +// httpmock.NewBytesResponse(200, httpmock.File("body.raw").Bytes()) func NewBytesResponse(status int, body []byte) *http.Response { return &http.Response{ Status: strconv.Itoa(status), @@ -395,7 +407,8 @@ func NewBytesResponse(status int, body []byte) *http.Response { // slice) and status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) +// +// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) func NewBytesResponder(status int, body []byte) Responder { return ResponderFromResponse(NewBytesResponse(status, body)) } @@ -405,7 +418,8 @@ func NewBytesResponder(status int, body []byte) Responder { // an http status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewJsonResponse(200, httpmock.File("body.json")) +// +// httpmock.NewJsonResponse(200, httpmock.File("body.json")) func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: revive encoded, err := json.Marshal(body) if err != nil { @@ -420,7 +434,8 @@ func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: // any that is encoded to json) and status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewJsonResponder(200, httpmock.File("body.json")) +// +// httpmock.NewJsonResponder(200, httpmock.File("body.json")) func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revive resp, err := NewJsonResponse(status, body) if err != nil { @@ -434,14 +449,16 @@ func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revi // It simplifies the call of RegisterResponder, avoiding the use of a // temporary variable and an error check, and so can be used as // NewStringResponder or NewBytesResponder in such context: -// httpmock.RegisterResponder( -// "GET", -// "/test/path", -// httpmock.NewJSONResponderOrPanic(200, &MyBody), -// ) +// +// httpmock.RegisterResponder( +// "GET", +// "/test/path", +// httpmock.NewJSONResponderOrPanic(200, &MyBody), +// ) // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewJsonResponderOrPanic(200, httpmock.File("body.json")) +// +// httpmock.NewJsonResponderOrPanic(200, httpmock.File("body.json")) func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive responder, err := NewJsonResponder(status, body) if err != nil { @@ -455,7 +472,8 @@ func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive // http status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewXmlResponse(200, httpmock.File("body.xml")) +// +// httpmock.NewXmlResponse(200, httpmock.File("body.xml")) func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: revive var ( encoded []byte @@ -478,7 +496,8 @@ func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: r // any that is encoded to xml) and status code. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewXmlResponder(200, httpmock.File("body.xml")) +// +// httpmock.NewXmlResponder(200, httpmock.File("body.xml")) func NewXmlResponder(status int, body any) (Responder, error) { // nolint: revive resp, err := NewXmlResponse(status, body) if err != nil { @@ -492,14 +511,16 @@ func NewXmlResponder(status int, body any) (Responder, error) { // nolint: reviv // It simplifies the call of RegisterResponder, avoiding the use of a // temporary variable and an error check, and so can be used as // NewStringResponder or NewBytesResponder in such context: -// httpmock.RegisterResponder( -// "GET", -// "/test/path", -// httpmock.NewXmlResponderOrPanic(200, &MyBody), -// ) +// +// httpmock.RegisterResponder( +// "GET", +// "/test/path", +// httpmock.NewXmlResponderOrPanic(200, &MyBody), +// ) // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewXmlResponderOrPanic(200, httpmock.File("body.xml")) +// +// httpmock.NewXmlResponderOrPanic(200, httpmock.File("body.xml")) func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive responder, err := NewXmlResponder(status, body) if err != nil { @@ -512,7 +533,8 @@ func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive // is suitable for use as an http response body. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewRespBodyFromString(httpmock.File("body.txt").String()) +// +// httpmock.NewRespBodyFromString(httpmock.File("body.txt").String()) func NewRespBodyFromString(body string) io.ReadCloser { return &dummyReadCloser{orig: body} } @@ -521,7 +543,8 @@ func NewRespBodyFromString(body string) io.ReadCloser { // that is suitable for use as an http response body. // // To pass the content of an existing file as body use httpmock.File as in: -// httpmock.NewRespBodyFromBytes(httpmock.File("body.txt").Bytes()) +// +// httpmock.NewRespBodyFromBytes(httpmock.File("body.txt").Bytes()) func NewRespBodyFromBytes(body []byte) io.ReadCloser { return &dummyReadCloser{orig: body} } diff --git a/response_test.go b/response_test.go index e6195e3..496deff 100644 --- a/response_test.go +++ b/response_test.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "io" - "io/ioutil" + "io/ioutil" //nolint: staticcheck "net/http" "path/filepath" "strings" diff --git a/transport.go b/transport.go index fc0956c..6cc19d5 100644 --- a/transport.go +++ b/transport.go @@ -434,10 +434,11 @@ func (m *MockTransport) checkMethod(method string) { // // If url contains query parameters, their order matters as well as // their content. All following URLs are here considered as different: -// http://z.tld?a=1&b=1 -// http://z.tld?b=1&a=1 -// http://z.tld?a&b -// http://z.tld?a=&b= +// +// http://z.tld?a=1&b=1 +// http://z.tld?b=1&a=1 +// http://z.tld?a&b +// http://z.tld?a=&b= // // If url begins with "=~", the following chars are considered as a // regular expression. If this regexp can not be compiled, it panics. @@ -457,23 +458,24 @@ func (m *MockTransport) checkMethod(method string) { // See RegisterRegexpResponder() to directly pass a *regexp.Regexp. // // Example: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() // -// httpmock.RegisterResponder("GET", "http://example.com/", -// httpmock.NewStringResponder(200, "hello world")) +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() +// +// httpmock.RegisterResponder("GET", "http://example.com/", +// httpmock.NewStringResponder(200, "hello world")) // -// httpmock.RegisterResponder("GET", "/path/only", -// httpmock.NewStringResponder("any host hello world", 200)) +// httpmock.RegisterResponder("GET", "/path/only", +// httpmock.NewStringResponder("any host hello world", 200)) // -// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, -// httpmock.NewStringResponder("any item get", 200)) +// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, +// httpmock.NewStringResponder("any item get", 200)) // -// // requests to http://example.com/ now return "hello world" and -// // requests to any host with path /path/only return "any host hello world" -// // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get" -// } +// // requests to http://example.com/ now return "hello world" and +// // requests to any host with path /path/only return "any host hello world" +// // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get" +// } // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible @@ -584,9 +586,10 @@ func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp // doesn't depend on query items order. // // If query is non-nil, its type can be: -// url.Values -// map[string]string -// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) +// +// url.Values +// map[string]string +// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) // // If the query type is not recognized or the string cannot be parsed // using net/url.ParseQuery, a panic() occurs. @@ -681,24 +684,25 @@ func sortedQuery(m url.Values) string { // Use it in conjunction with NewNotFoundResponder to ensure that all // routes have been mocked: // -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... -// func TestMyApp(t *testing.T) { -// ... -// // Calls testing.Fatal with the name of Responder-less route and -// // the stack trace of the call. -// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // Calls testing.Fatal with the name of Responder-less route and +// // the stack trace of the call. +// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) // // Will abort the current test and print something like: -// transport_test.go:735: Called from net/http.Get() -// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 -// github.com/jarcoal/httpmock.TestCheckStackTracer() -// at /go/src/testing/testing.go:865 -// testing.tRunner() -// at /go/src/runtime/asm_amd64.s:1337 +// +// transport_test.go:735: Called from net/http.Get() +// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 +// github.com/jarcoal/httpmock.TestCheckStackTracer() +// at /go/src/testing/testing.go:865 +// testing.tRunner() +// at /go/src/runtime/asm_amd64.s:1337 // // If responder is passed as nil, the default behavior // (httpmock.ConnectionFailure) is re-enabled. @@ -739,14 +743,16 @@ func (m *MockTransport) ZeroCallCounters() { // As a special case, regexp responders generate 2 entries for each // call. One for the call caught and the other for the rule that // matched. For example: -// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) -// http.Get("http://z.com") +// +// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) +// http.Get("http://z.com") // // will generate the following result: -// map[string]int{ -// `GET http://z.com`: 1, -// `GET =~z\.com\z`: 1, -// } +// +// map[string]int{ +// `GET http://z.com`: 1, +// `GET =~z\.com\z`: 1, +// } func (m *MockTransport) GetCallCountInfo() map[string]int { m.mu.RLock() res := make(map[string]int, len(m.callCountInfo)) @@ -785,22 +791,25 @@ var oldClientsLock sync.Mutex // http.DefaultClient with httpmock.DefaultTransport. // // To enable mocks for a test, simply activate at the beginning of a test: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// // all http requests using http.DefaultTransport will now be intercepted -// } +// +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// // all http requests using http.DefaultTransport will now be intercepted +// } // // If you want all of your tests in a package to be mocked, just call // Activate from init(): -// func init() { -// httpmock.Activate() -// } +// +// func init() { +// httpmock.Activate() +// } // // or using a TestMain function: -// func TestMain(m *testing.M) { -// httpmock.Activate() -// os.Exit(m.Run()) -// } +// +// func TestMain(m *testing.M) { +// httpmock.Activate() +// os.Exit(m.Run()) +// } func Activate() { if Disabled() { return @@ -821,8 +830,9 @@ func Activate() { // // To enable mocks for a test using a custom client, activate at the // beginning of a test: -// client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}} -// httpmock.ActivateNonDefault(client) +// +// client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}} +// httpmock.ActivateNonDefault(client) func ActivateNonDefault(client *http.Client) { if Disabled() { return @@ -846,14 +856,16 @@ func ActivateNonDefault(client *http.Client) { // As a special case, regexp responders generate 2 entries for each // call. One for the call caught and the other for the rule that // matched. For example: -// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) -// http.Get("http://z.com") +// +// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body")) +// http.Get("http://z.com") // // will generate the following result: -// map[string]int{ -// `GET http://z.com`: 1, -// `GET =~z\.com\z`: 1, -// } +// +// map[string]int{ +// `GET http://z.com`: 1, +// `GET =~z\.com\z`: 1, +// } func GetCallCountInfo() map[string]int { return DefaultTransport.GetCallCountInfo() } @@ -869,20 +881,22 @@ func GetTotalCallCount() int { // // Usually you'll call it in a defer right after activating the mock // environment: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.Deactivate() // -// // when this test ends, the mock environment will close -// } +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.Deactivate() +// +// // when this test ends, the mock environment will close +// } // // Since go 1.14 you can also use (*testing.T).Cleanup() method as in: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// t.Cleanup(httpmock.Deactivate) // -// // when this test ends, the mock environment will close -// } +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// t.Cleanup(httpmock.Deactivate) +// +// // when this test ends, the mock environment will close +// } // // useful in test helpers to save your callers from calling defer themselves. func Deactivate() { @@ -928,10 +942,11 @@ func DeactivateAndReset() { // // If url contains query parameters, their order matters as well as // their content. All following URLs are here considered as different: -// http://z.tld?a=1&b=1 -// http://z.tld?b=1&a=1 -// http://z.tld?a&b -// http://z.tld?a=&b= +// +// http://z.tld?a=1&b=1 +// http://z.tld?b=1&a=1 +// http://z.tld?a&b +// http://z.tld?a=&b= // // If url begins with "=~", the following chars are considered as a // regular expression. If this regexp can not be compiled, it panics. @@ -951,23 +966,24 @@ func DeactivateAndReset() { // See RegisterRegexpResponder() to directly pass a *regexp.Regexp. // // Example: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() // -// httpmock.RegisterResponder("GET", "http://example.com/", -// httpmock.NewStringResponder(200, "hello world")) +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() +// +// httpmock.RegisterResponder("GET", "http://example.com/", +// httpmock.NewStringResponder(200, "hello world")) // -// httpmock.RegisterResponder("GET", "/path/only", -// httpmock.NewStringResponder("any host hello world", 200)) +// httpmock.RegisterResponder("GET", "/path/only", +// httpmock.NewStringResponder("any host hello world", 200)) // -// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, -// httpmock.NewStringResponder("any item get", 200)) +// httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`, +// httpmock.NewStringResponder("any item get", 200)) // -// // requests to http://example.com/ now return "hello world" and -// // requests to any host with path /path/only return "any host hello world" -// // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get" -// } +// // requests to http://example.com/ now return "hello world" and +// // requests to any host with path /path/only return "any host hello world" +// // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get" +// } // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible @@ -1011,9 +1027,10 @@ func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder // doesn't depends on query items order. // // query type can be: -// url.Values -// map[string]string -// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) +// +// url.Values +// map[string]string +// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) // // If the query type is not recognized or the string cannot be parsed // using net/url.ParseQuery, a panic() occurs. @@ -1029,53 +1046,56 @@ func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder // nothing if it does not already exist. // // Example using a net/url.Values: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() -// -// expectedQuery := net.Values{ -// "a": []string{"3", "1", "8"}, -// "b": []string{"4", "2"}, -// } -// httpmock.RegisterResponderWithQueryValues( -// "GET", "http://example.com/", expectedQuery, -// httpmock.NewStringResponder("hello world", 200)) -// -// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 -// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 -// // now return 'hello world' -// } +// +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() +// +// expectedQuery := net.Values{ +// "a": []string{"3", "1", "8"}, +// "b": []string{"4", "2"}, +// } +// httpmock.RegisterResponderWithQueryValues( +// "GET", "http://example.com/", expectedQuery, +// httpmock.NewStringResponder("hello world", 200)) +// +// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 +// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 +// // now return 'hello world' +// } // // or using a map[string]string: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() // -// expectedQuery := map[string]string{ -// "a": "1", -// "b": "2" -// } -// httpmock.RegisterResponderWithQuery( -// "GET", "http://example.com/", expectedQuery, -// httpmock.NewStringResponder("hello world", 200)) +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() // -// // requests to http://example.com?a=1&b=2 and http://example.com?b=2&a=1 now return 'hello world' -// } +// expectedQuery := map[string]string{ +// "a": "1", +// "b": "2" +// } +// httpmock.RegisterResponderWithQuery( +// "GET", "http://example.com/", expectedQuery, +// httpmock.NewStringResponder("hello world", 200)) +// +// // requests to http://example.com?a=1&b=2 and http://example.com?b=2&a=1 now return 'hello world' +// } // // or using a query string: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() // -// expectedQuery := "a=3&b=4&b=2&a=1&a=8" -// httpmock.RegisterResponderWithQueryValues( -// "GET", "http://example.com/", expectedQuery, -// httpmock.NewStringResponder("hello world", 200)) +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() +// +// expectedQuery := "a=3&b=4&b=2&a=1&a=8" +// httpmock.RegisterResponderWithQueryValues( +// "GET", "http://example.com/", expectedQuery, +// httpmock.NewStringResponder("hello world", 200)) // -// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 -// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 -// // now return 'hello world' -// } +// // requests to http://example.com?a=1&a=3&a=8&b=2&b=4 +// // and to http://example.com?b=4&a=2&b=2&a=8&a=1 +// // now return 'hello world' +// } // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible @@ -1091,13 +1111,14 @@ func RegisterResponderWithQuery(method, path string, query any, responder Respon // // In some cases you may not want all URLs to be mocked, in which case // you can do this: -// func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() -// httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip) // -// // any requests that don't have a registered URL will be fetched normally -// } +// func TestFetchArticles(t *testing.T) { +// httpmock.Activate() +// defer httpmock.DeactivateAndReset() +// httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip) +// +// // any requests that don't have a registered URL will be fetched normally +// } func RegisterNoResponder(responder Responder) { DefaultTransport.RegisterNoResponder(responder) } @@ -1110,17 +1131,18 @@ var ErrSubmatchNotFound = errors.New("submatch not found") // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as a // string. Example: -// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, -// func(req *http.Request) (*http.Response, error) { -// name, err := GetSubmatch(req, 1) // 1=first regexp submatch -// if err != nil { -// return nil, err -// } -// return NewJsonResponse(200, map[string]any{ -// "id": 123, -// "name": name, -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, +// func(req *http.Request) (*http.Response, error) { +// name, err := GetSubmatch(req, 1) // 1=first regexp submatch +// if err != nil { +// return nil, err +// } +// return NewJsonResponse(200, map[string]any{ +// "id": 123, +// "name": name, +// }) +// }) // // It panics if n < 1. See MustGetSubmatch to avoid testing the // returned error. @@ -1141,17 +1163,18 @@ func GetSubmatch(req *http.Request, n int) (string, error) { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as an // int64. Example: -// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, -// func(req *http.Request) (*http.Response, error) { -// id, err := GetSubmatchAsInt(req, 1) // 1=first regexp submatch -// if err != nil { -// return nil, err -// } -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, +// func(req *http.Request) (*http.Response, error) { +// id, err := GetSubmatchAsInt(req, 1) // 1=first regexp submatch +// if err != nil { +// return nil, err +// } +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// }) +// }) // // It panics if n < 1. See MustGetSubmatchAsInt to avoid testing the // returned error. @@ -1167,17 +1190,18 @@ func GetSubmatchAsInt(req *http.Request, n int) (int64, error) { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as a // uint64. Example: -// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, -// func(req *http.Request) (*http.Response, error) { -// id, err := GetSubmatchAsUint(req, 1) // 1=first regexp submatch -// if err != nil { -// return nil, err -// } -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, +// func(req *http.Request) (*http.Response, error) { +// id, err := GetSubmatchAsUint(req, 1) // 1=first regexp submatch +// if err != nil { +// return nil, err +// } +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// }) +// }) // // It panics if n < 1. See MustGetSubmatchAsUint to avoid testing the // returned error. @@ -1193,18 +1217,19 @@ func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as a // float64. Example: -// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, -// func(req *http.Request) (*http.Response, error) { -// height, err := GetSubmatchAsFloat(req, 1) // 1=first regexp submatch -// if err != nil { -// return nil, err -// } -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// "height": height, -// }) -// }) +// +// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, +// func(req *http.Request) (*http.Response, error) { +// height, err := GetSubmatchAsFloat(req, 1) // 1=first regexp submatch +// if err != nil { +// return nil, err +// } +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// "height": height, +// }) +// }) // // It panics if n < 1. See MustGetSubmatchAsFloat to avoid testing the // returned error. @@ -1221,14 +1246,15 @@ func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) { // installed by RegisterRegexpResponder or RegisterResponder + "=~" // URL prefix. It allows to retrieve the n-th submatch of the matching // regexp, as a string. Example: -// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, -// func(req *http.Request) (*http.Response, error) { -// name := MustGetSubmatch(req, 1) // 1=first regexp submatch -// return NewJsonResponse(200, map[string]any{ -// "id": 123, -// "name": name, -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, +// func(req *http.Request) (*http.Response, error) { +// name := MustGetSubmatch(req, 1) // 1=first regexp submatch +// return NewJsonResponse(200, map[string]any{ +// "id": 123, +// "name": name, +// }) +// }) // // It panics if n < 1. func MustGetSubmatch(req *http.Request, n int) string { @@ -1245,14 +1271,15 @@ func MustGetSubmatch(req *http.Request, n int) string { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as an // int64. Example: -// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, -// func(req *http.Request) (*http.Response, error) { -// id := MustGetSubmatchAsInt(req, 1) // 1=first regexp submatch -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, +// func(req *http.Request) (*http.Response, error) { +// id := MustGetSubmatchAsInt(req, 1) // 1=first regexp submatch +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// }) +// }) // // It panics if n < 1. func MustGetSubmatchAsInt(req *http.Request, n int) int64 { @@ -1269,14 +1296,15 @@ func MustGetSubmatchAsInt(req *http.Request, n int) int64 { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as a // uint64. Example: -// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, -// func(req *http.Request) (*http.Response, error) { -// id, err := MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// }) -// }) +// +// RegisterResponder("GET", `=~^/item/id/(\d+)\z`, +// func(req *http.Request) (*http.Response, error) { +// id, err := MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// }) +// }) // // It panics if n < 1. func MustGetSubmatchAsUint(req *http.Request, n int) uint64 { @@ -1293,15 +1321,16 @@ func MustGetSubmatchAsUint(req *http.Request, n int) uint64 { // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It // allows to retrieve the n-th submatch of the matching regexp, as a // float64. Example: -// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, -// func(req *http.Request) (*http.Response, error) { -// height := MustGetSubmatchAsFloat(req, 1) // 1=first regexp submatch -// return NewJsonResponse(200, map[string]any{ -// "id": id, -// "name": "The beautiful name", -// "height": height, -// }) -// }) +// +// RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, +// func(req *http.Request) (*http.Response, error) { +// height := MustGetSubmatchAsFloat(req, 1) // 1=first regexp submatch +// return NewJsonResponse(200, map[string]any{ +// "id": id, +// "name": "The beautiful name", +// "height": height, +// }) +// }) // // It panics if n < 1. func MustGetSubmatchAsFloat(req *http.Request, n int) float64 { diff --git a/util_test.go b/util_test.go index 27220eb..194b2f4 100644 --- a/util_test.go +++ b/util_test.go @@ -1,7 +1,7 @@ package httpmock_test import ( - "io/ioutil" + "io/ioutil" //nolint: staticcheck "net/http" "os" "testing" From 638f16c7002a28ec479fb817d266c96a50c2e17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Fri, 26 Aug 2022 12:23:28 +0200 Subject: [PATCH 2/2] docs: uses new go1.19 doc features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime Soulé --- file.go | 21 ++-- response.go | 157 +++++++++++++------------- transport.go | 302 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 269 insertions(+), 211 deletions(-) diff --git a/file.go b/file.go index 90d3964..c88a236 100644 --- a/file.go +++ b/file.go @@ -13,7 +13,7 @@ import ( // file := httpmock.File("file.txt") // fmt.Printf("file: %s\n", file) // -// prints the content of file "file.txt" as String() method is used. +// prints the content of file "file.txt" as [File.String] method is used. // // To print the file name, and not its content, simply do: // @@ -21,10 +21,10 @@ import ( // fmt.Printf("file: %s\n", string(file)) type File string -// MarshalJSON implements json.Marshaler. +// MarshalJSON implements [encoding/json.Marshaler]. // -// Useful to be used in conjunction with NewJsonResponse() or -// NewJsonResponder() as in: +// Useful to be used in conjunction with [NewJsonResponse] or +// [NewJsonResponder] as in: // // httpmock.NewJsonResponder(200, httpmock.File("body.json")) func (f File) MarshalJSON() ([]byte, error) { @@ -38,8 +38,8 @@ func (f File) bytes() ([]byte, error) { // Bytes returns the content of file as a []byte. If an error occurs // during the opening or reading of the file, it panics. // -// Useful to be used in conjunction with NewBytesResponse() or -// NewBytesResponder() as in: +// Useful to be used in conjunction with [NewBytesResponse] or +// [NewBytesResponder] as in: // // httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) func (f File) Bytes() []byte { @@ -50,11 +50,12 @@ func (f File) Bytes() []byte { return b } -// String returns the content of file as a string. If an error occurs -// during the opening or reading of the file, it panics. +// String implements [fmt.Stringer] and returns the content of file as +// a string. If an error occurs during the opening or reading of the +// file, it panics. // -// Useful to be used in conjunction with NewStringResponse() or -// NewStringResponder() as in: +// Useful to be used in conjunction with [NewStringResponse] or +// [NewStringResponder] as in: // // httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) func (f File) String() string { diff --git a/response.go b/response.go index 45db89e..caf1c11 100644 --- a/response.go +++ b/response.go @@ -33,7 +33,7 @@ type suggestedKeyType struct{} var suggestedKey = suggestedKeyType{} -// Responder is a callback that receives an http request and returns +// Responder is a callback that receives an [*http.Request] and returns // a mocked response. type Responder func(*http.Request) (*http.Response, error) @@ -54,11 +54,11 @@ func (r Responder) times(name string, n int, fn ...func(...any)) Responder { } } -// Times returns a Responder callable n times before returning an -// error. If the Responder is called more than n times and fn is +// Times returns a [Responder] callable n times before returning an +// error. If the [Responder] is called more than n times and fn is // passed and non-nil, it acts as the fn parameter of -// NewNotFoundResponder, allowing to dump the stack trace to localize -// the origin of the call. +// [NewNotFoundResponder], allowing to dump the stack trace to +// localize the origin of the call. // // import ( // "testing" @@ -76,9 +76,9 @@ func (r Responder) Times(n int, fn ...func(...any)) Responder { return r.times("Times", n, fn...) } -// Once returns a new Responder callable once before returning an -// error. If the Responder is called 2 or more times and fn is passed -// and non-nil, it acts as the fn parameter of NewNotFoundResponder, +// Once returns a new [Responder] callable once before returning an +// error. If the [Responder] is called 2 or more times and fn is passed +// and non-nil, it acts as the fn parameter of [NewNotFoundResponder], // allowing to dump the stack trace to localize the origin of the // call. // @@ -98,10 +98,10 @@ func (r Responder) Once(fn ...func(...any)) Responder { return r.times("Once", 1, fn...) } -// Trace returns a new Responder that allows to easily trace the calls -// of the original Responder using fn. It can be used in conjunction +// Trace returns a new [Responder] that allows to easily trace the calls +// of the original [Responder] using fn. It can be used in conjunction // with the testing package as in the example below with the help of -// (*testing.T).Log method: +// [*testing.T.Log] method: // // import ( // "testing" @@ -123,7 +123,7 @@ func (r Responder) Trace(fn func(...any)) Responder { } } -// Delay returns a new Responder that calls the original r Responder +// Delay returns a new [Responder] that calls the original r Responder // after a delay of d. // // import ( @@ -153,7 +153,7 @@ func (r Responder) similar(other Responder) bool { return reflect.ValueOf(r).Pointer() == reflect.ValueOf(other).Pointer() } -// Then returns a new Responder that calls r on first invocation, then +// Then returns a new [Responder] that calls r on first invocation, then // next on following ones, except when Then is chained, in this case // next is called only once: // @@ -175,6 +175,8 @@ func (r Responder) similar(other Responder) bool { // // A.Then(B).Then(C) // is OK // A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call +// +// See also [ResponderFromMultipleResponses]. func (r Responder) Then(next Responder) (x Responder) { var done int var mu sync.Mutex @@ -215,16 +217,16 @@ func (r Responder) Then(next Responder) (x Responder) { return } -// ResponderFromResponse wraps an *http.Response in a Responder. +// ResponderFromResponse wraps an [*http.Response] in a [Responder]. // // Be careful, except for responses generated by httpmock -// (NewStringResponse and NewBytesResponse functions) for which there -// is no problems, it is the caller responsibility to ensure the +// ([NewStringResponse] and [NewBytesResponse] functions) for which +// there is no problems, it is the caller responsibility to ensure the // response body can be read several times and concurrently if needed, -// as it is shared among all Responder returned responses. +// as it is shared among all [Responder] returned responses. // -// For home-made responses, NewRespBodyFromString and -// NewRespBodyFromBytes functions can be used to produce response +// For home-made responses, [NewRespBodyFromString] and +// [NewRespBodyFromBytes] functions can be used to produce response // bodies that can be read several times and concurrently. func ResponderFromResponse(resp *http.Response) Responder { return func(req *http.Request) (*http.Response, error) { @@ -240,26 +242,26 @@ func ResponderFromResponse(resp *http.Response) Responder { } } -// ResponderFromMultipleResponses wraps an *http.Response list in a Responder. +// ResponderFromMultipleResponses wraps an [*http.Response] list in a +// [Responder]. // // Each response will be returned in the order of the provided list. -// If the responder is called more than the size of the provided list, an error -// will be thrown. +// If the [Responder] is called more than the size of the provided +// list, an error will be thrown. // // Be careful, except for responses generated by httpmock -// (NewStringResponse and NewBytesResponse functions) for which there -// is no problems, it is the caller responsibility to ensure the +// ([NewStringResponse] and [NewBytesResponse] functions) for which +// there is no problems, it is the caller responsibility to ensure the // response body can be read several times and concurrently if needed, -// as it is shared among all Responder returned responses. +// as it is shared among all [Responder] returned responses. // -// For home-made responses, NewRespBodyFromString and -// NewRespBodyFromBytes functions can be used to produce response +// For home-made responses, [NewRespBodyFromString] and +// [NewRespBodyFromBytes] functions can be used to produce response // bodies that can be read several times and concurrently. // -// If all responses have been returned and fn is passed -// and non-nil, it acts as the fn parameter of NewNotFoundResponder, -// allowing to dump the stack trace to localize the origin of the -// call. +// If all responses have been returned and fn is passed and non-nil, +// it acts as the fn parameter of [NewNotFoundResponder], allowing to +// dump the stack trace to localize the origin of the call. // // import ( // "github.com/jarcoal/httpmock" @@ -279,6 +281,8 @@ func ResponderFromResponse(resp *http.Response) Responder { // t.Log), // ) // } +// +// See also [Responder.Then]. func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...any)) Responder { responseIndex := 0 mutex := sync.Mutex{} @@ -306,7 +310,7 @@ func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...an } } -// NewErrorResponder creates a Responder that returns an empty request and the +// NewErrorResponder creates a [Responder] that returns an empty request and the // given error. This can be used to e.g. imitate more deep http errors for the // client. func NewErrorResponder(err error) Responder { @@ -315,12 +319,12 @@ func NewErrorResponder(err error) Responder { } } -// NewNotFoundResponder creates a Responder typically used in -// conjunction with RegisterNoResponder() function and testing -// package, to be proactive when a Responder is not found. fn is +// NewNotFoundResponder creates a [Responder] typically used in +// conjunction with [RegisterNoResponder] function and [testing] +// package, to be proactive when a [Responder] is not found. fn is // called with a unique string parameter containing the name of the // missing route and the stack trace to localize the origin of the -// call. If fn returns (= if it does not panic), the responder returns +// call. If fn returns (= if it does not panic), the [Responder] returns // an error of the form: "Responder not found for GET http://foo.bar/path". // Note that fn can be nil. // @@ -362,10 +366,10 @@ func NewNotFoundResponder(fn func(...any)) Responder { } } -// NewStringResponse creates an *http.Response with a body based on -// the given string. Also accepts an http status code. +// NewStringResponse creates an [*http.Response] with a body based on +// the given string. Also accepts an HTTP status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewStringResponse(200, httpmock.File("body.txt").String()) func NewStringResponse(status int, body string) *http.Response { @@ -378,19 +382,20 @@ func NewStringResponse(status int, body string) *http.Response { } } -// NewStringResponder creates a Responder from a given body (as a string) and status code. +// NewStringResponder creates a [Responder] from a given body (as a +// string) and status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewStringResponder(200, httpmock.File("body.txt").String()) func NewStringResponder(status int, body string) Responder { return ResponderFromResponse(NewStringResponse(status, body)) } -// NewBytesResponse creates an *http.Response with a body based on the -// given bytes. Also accepts an http status code. +// NewBytesResponse creates an [*http.Response] with a body based on the +// given bytes. Also accepts an HTTP status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewBytesResponse(200, httpmock.File("body.raw").Bytes()) func NewBytesResponse(status int, body []byte) *http.Response { @@ -403,21 +408,21 @@ func NewBytesResponse(status int, body []byte) *http.Response { } } -// NewBytesResponder creates a Responder from a given body (as a byte +// NewBytesResponder creates a [Responder] from a given body (as a byte // slice) and status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes()) func NewBytesResponder(status int, body []byte) Responder { return ResponderFromResponse(NewBytesResponse(status, body)) } -// NewJsonResponse creates an *http.Response with a body that is a -// json encoded representation of the given any. Also accepts -// an http status code. +// NewJsonResponse creates an [*http.Response] with a body that is a +// JSON encoded representation of the given any. Also accepts +// an HTTP status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewJsonResponse(200, httpmock.File("body.json")) func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: revive @@ -430,10 +435,10 @@ func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: return response, nil } -// NewJsonResponder creates a Responder from a given body (as an -// any that is encoded to json) and status code. +// NewJsonResponder creates a [Responder] from a given body (as an +// any that is encoded to JSON) and status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewJsonResponder(200, httpmock.File("body.json")) func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revive @@ -444,19 +449,20 @@ func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revi return ResponderFromResponse(resp), nil } -// NewJsonResponderOrPanic is like NewJsonResponder but panics in case of error. +// NewJsonResponderOrPanic is like [NewJsonResponder] but panics in +// case of error. // -// It simplifies the call of RegisterResponder, avoiding the use of a +// It simplifies the call of [RegisterResponder], avoiding the use of a // temporary variable and an error check, and so can be used as -// NewStringResponder or NewBytesResponder in such context: +// [NewStringResponder] or [NewBytesResponder] in such context: // // httpmock.RegisterResponder( // "GET", // "/test/path", -// httpmock.NewJSONResponderOrPanic(200, &MyBody), +// httpmock.NewJsonResponderOrPanic(200, &MyBody), // ) // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewJsonResponderOrPanic(200, httpmock.File("body.json")) func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive @@ -467,11 +473,11 @@ func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive return responder } -// NewXmlResponse creates an *http.Response with a body that is an xml -// encoded representation of the given any. Also accepts an -// http status code. +// NewXmlResponse creates an [*http.Response] with a body that is an +// XML encoded representation of the given any. Also accepts an HTTP +// status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewXmlResponse(200, httpmock.File("body.xml")) func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: revive @@ -492,10 +498,10 @@ func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: r return response, nil } -// NewXmlResponder creates a Responder from a given body (as an -// any that is encoded to xml) and status code. +// NewXmlResponder creates a [Responder] from a given body (as an +// any that is encoded to XML) and status code. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewXmlResponder(200, httpmock.File("body.xml")) func NewXmlResponder(status int, body any) (Responder, error) { // nolint: revive @@ -506,11 +512,12 @@ func NewXmlResponder(status int, body any) (Responder, error) { // nolint: reviv return ResponderFromResponse(resp), nil } -// NewXmlResponderOrPanic is like NewXmlResponder but panics in case of error. +// NewXmlResponderOrPanic is like [NewXmlResponder] but panics in case +// of error. // -// It simplifies the call of RegisterResponder, avoiding the use of a +// It simplifies the call of [RegisterResponder], avoiding the use of a // temporary variable and an error check, and so can be used as -// NewStringResponder or NewBytesResponder in such context: +// [NewStringResponder] or [NewBytesResponder] in such context: // // httpmock.RegisterResponder( // "GET", @@ -518,7 +525,7 @@ func NewXmlResponder(status int, body any) (Responder, error) { // nolint: reviv // httpmock.NewXmlResponderOrPanic(200, &MyBody), // ) // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewXmlResponderOrPanic(200, httpmock.File("body.xml")) func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive @@ -529,20 +536,20 @@ func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive return responder } -// NewRespBodyFromString creates an io.ReadCloser from a string that -// is suitable for use as an http response body. +// NewRespBodyFromString creates an [io.ReadCloser] from a string that +// is suitable for use as an HTTP response body. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewRespBodyFromString(httpmock.File("body.txt").String()) func NewRespBodyFromString(body string) io.ReadCloser { return &dummyReadCloser{orig: body} } -// NewRespBodyFromBytes creates an io.ReadCloser from a byte slice -// that is suitable for use as an http response body. +// NewRespBodyFromBytes creates an [io.ReadCloser] from a byte slice +// that is suitable for use as an HTTP response body. // -// To pass the content of an existing file as body use httpmock.File as in: +// To pass the content of an existing file as body use [File] as in: // // httpmock.NewRespBodyFromBytes(httpmock.File("body.txt").Bytes()) func NewRespBodyFromBytes(body []byte) io.ReadCloser { diff --git a/transport.go b/transport.go index 6cc19d5..844b99f 100644 --- a/transport.go +++ b/transport.go @@ -39,13 +39,14 @@ func methodProbablyWrong(method string) bool { } // ConnectionFailure is a responder that returns a connection failure. -// This is the default responder, and is called when no other matching -// responder is found. +// This is the default responder and is called when no other matching +// responder is found. See [RegisterNoResponder] to override this +// default behavior. func ConnectionFailure(*http.Request) (*http.Response, error) { return nil, NoResponderFound } -// NewMockTransport creates a new *MockTransport with no responders. +// NewMockTransport creates a new [*MockTransport] with no responders. func NewMockTransport() *MockTransport { return &MockTransport{ responders: make(map[internal.RouteKey]Responder), @@ -60,10 +61,10 @@ type regexpResponder struct { responder Responder } -// MockTransport implements http.RoundTripper, which fulfills single -// http requests issued by an http.Client. This implementation -// doesn't actually make the call, instead deferring to the registered -// list of responders. +// MockTransport implements [http.RoundTripper] interface, which +// fulfills single HTTP requests issued by an [http.Client]. This +// implementation doesn't actually make the call, instead deferring to +// the registered list of responders. type MockTransport struct { // DontCheckMethod disables standard methods check. By default, if // a responder is registered using a lower-cased method among CONNECT, @@ -170,9 +171,9 @@ func (m *MockTransport) findResponder(method string, url *url.URL) ( } // RoundTrip receives HTTP requests and routes them to the appropriate -// responder. It is required to implement the http.RoundTripper +// responder. It is required to implement the [http.RoundTripper] // interface. You will not interact with this directly, instead the -// *http.Client you are using will call it for you. +// [*http.Client] you are using will call it for you. func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { method := req.Method if method == "" { @@ -274,8 +275,8 @@ func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { } // NumResponders returns the number of responders currently in use. -// The responder registered with RegisterNoResponder() is not taken -// into account. +// The responder registered with [MockTransport.RegisterNoResponder] +// is not taken into account. func (m *MockTransport) NumResponders() int { m.mu.RLock() defer m.mu.RUnlock() @@ -287,7 +288,9 @@ func (m *MockTransport) NumResponders() int { // Non-regexp responders are listed first in alphabetical order // (sorted by URL then METHOD), then regexp responders in the order // they have been registered. -// The responder registered with RegisterNoResponder() is not listed. +// +// The responder registered with [MockTransport.RegisterNoResponder] +// is not listed. func (m *MockTransport) Responders() []string { m.mu.RLock() defer m.mu.RUnlock() @@ -443,19 +446,22 @@ func (m *MockTransport) checkMethod(method string) { // If url begins with "=~", the following chars are considered as a // regular expression. If this regexp can not be compiled, it panics. // Note that the "=~" prefix remains in statistics returned by -// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp -// responders are tested in the order they are registered. Registering -// an already existing regexp responder (same method & same regexp -// string) replaces its responder, but does not change its position. +// [MockTransport.GetCallCountInfo]. As 2 regexps can match the same +// URL, the regexp responders are tested in the order they are +// registered. Registering an already existing regexp responder (same +// method & same regexp string) replaces its responder, but does not +// change its position. // // Registering an already existing responder resets the corresponding -// statistics as returned by GetCallCountInfo(). +// statistics as returned by [MockTransport.GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does -// nothing if it does not already exist. +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by +// [MockTransport.GetCallCountInfo]. It does nothing if it does not +// already exist. // -// See RegisterRegexpResponder() to directly pass a *regexp.Regexp. +// See [MockTransport.RegisterRegexpResponder] to directly pass a +// [*regexp.Regexp]. // // Example: // @@ -555,17 +561,19 @@ found: // tested in the order they are registered. Registering an already // existing regexp responder (same method & same regexp string) // replaces its responder, but does not change its position, and -// resets the corresponding statistics as returned by GetCallCountInfo(). +// resets the corresponding statistics as returned by +// [MockTransport.GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does -// nothing if it does not already exist. +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by +// [MockTransport.MockTransportGetCallCountInfo]. It does nothing if +// it does not already exist. // // A "=~" prefix is added to the stringified regexp in the statistics -// returned by GetCallCountInfo(). +// returned by [MockTransport.GetCallCountInfo]. // -// See RegisterResponder function and the "=~" prefix in its url -// parameter to avoid compiling the regexp by yourself. +// See [MockTransport.RegisterResponder] function and the "=~" prefix +// in its url parameter to avoid compiling the regexp by yourself. // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible @@ -582,27 +590,29 @@ func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp }) } -// RegisterResponderWithQuery is same as RegisterResponder, but it -// doesn't depend on query items order. +// RegisterResponderWithQuery is same as +// [MockTransport.RegisterResponder], but it doesn't depend on query +// items order. // // If query is non-nil, its type can be: // -// url.Values -// map[string]string -// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) +// - [url.Values] +// - map[string]string +// - string, a query string like "a=12&a=13&b=z&c" (see [url.ParseQuery] function) // // If the query type is not recognized or the string cannot be parsed -// using net/url.ParseQuery, a panic() occurs. +// using [url.ParseQuery], a panic() occurs. // -// Unlike RegisterResponder, path cannot be prefixed by "=~" to say it -// is a regexp. If it is, a panic occurs. +// Unlike [MockTransport.RegisterResponder], path cannot be prefixed +// by "=~" to say it is a regexp. If it is, a panic occurs. // // Registering an already existing responder resets the corresponding -// statistics as returned by GetCallCountInfo(). +// statistics as returned by [MockTransport.GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does -// nothing if it does not already exist. +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by +// [MockTransport.GetCallCountInfo]. It does nothing if it does not +// already exist. // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible @@ -678,24 +688,19 @@ func sortedQuery(m url.Values) string { } // RegisterNoResponder is used to register a responder that is called -// if no other responder is found. The default is httpmock.ConnectionFailure -// that returns an error able to indicate a possible method mismatch. +// if no other responders are found. The default is [ConnectionFailure] +// that returns a connection error. // -// Use it in conjunction with NewNotFoundResponder to ensure that all +// Use it in conjunction with [NewNotFoundResponder] to ensure that all // routes have been mocked: // -// import ( -// "testing" -// "github.com/jarcoal/httpmock" -// ) -// ... // func TestMyApp(t *testing.T) { // ... // // Calls testing.Fatal with the name of Responder-less route and // // the stack trace of the call. -// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) +// mock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) // -// Will abort the current test and print something like: +// will abort the current test and print something like: // // transport_test.go:735: Called from net/http.Get() // at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 @@ -705,7 +710,17 @@ func sortedQuery(m url.Values) string { // at /go/src/runtime/asm_amd64.s:1337 // // If responder is passed as nil, the default behavior -// (httpmock.ConnectionFailure) is re-enabled. +// ([ConnectionFailure]) is re-enabled. +// +// In some cases you may not want all URLs to be mocked, in which case +// you can do this: +// +// func TestFetchArticles(t *testing.T) { +// ... +// mock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip) +// +// // any requests that don't have a registered URL will be fetched normally +// } func (m *MockTransport) RegisterNoResponder(responder Responder) { m.mu.Lock() m.noResponder = responder @@ -713,7 +728,7 @@ func (m *MockTransport) RegisterNoResponder(responder Responder) { } // Reset removes all registered responders (including the no -// responder) from the MockTransport. It zeroes call counters too. +// responder) from the [MockTransport]. It zeroes call counters too. func (m *MockTransport) Reset() { m.mu.Lock() m.responders = make(map[internal.RouteKey]Responder) @@ -734,10 +749,10 @@ func (m *MockTransport) ZeroCallCounters() { m.mu.Unlock() } -// GetCallCountInfo gets the info on all the calls httpmock has caught +// GetCallCountInfo gets the info on all the calls m has caught // since it was activated or reset. The info is returned as a map of // the calling keys with the number of calls made to them as their -// value. The key is the method, a space, and the url all concatenated +// value. The key is the method, a space, and the URL all concatenated // together. // // As a special case, regexp responders generate 2 entries for each @@ -763,20 +778,22 @@ func (m *MockTransport) GetCallCountInfo() map[string]int { return res } -// GetTotalCallCount returns the totalCallCount. +// GetTotalCallCount gets the total number of calls m has taken +// since it was activated or reset. func (m *MockTransport) GetTotalCallCount() int { m.mu.RLock() defer m.mu.RUnlock() return m.totalCallCount } -// DefaultTransport is the default mock transport used by Activate, -// Deactivate, Reset, DeactivateAndReset, RegisterResponder, and -// RegisterNoResponder. +// DefaultTransport is the default mock transport used by [Activate], +// [Deactivate], [Reset], [DeactivateAndReset], [RegisterResponder], +// [RegisterRegexpResponder], [RegisterResponderWithQuery] and +// [RegisterNoResponder]. var DefaultTransport = NewMockTransport() // InitialTransport is a cache of the original transport used so we -// can put it back when Deactivate is called. +// can put it back when [Deactivate] is called. var InitialTransport = http.DefaultTransport // oldClients is used to handle custom http clients (i.e clients other @@ -787,8 +804,8 @@ var oldClients = map[*http.Client]http.RoundTripper{} var oldClientsLock sync.Mutex // Activate starts the mock environment. This should be called before -// your tests run. Under the hood this replaces the Transport on the -// http.DefaultClient with httpmock.DefaultTransport. +// your tests run. Under the hood this replaces the [http.Client.Transport] +// field of [http.DefaultClient] with [DefaultTransport]. // // To enable mocks for a test, simply activate at the beginning of a test: // @@ -798,7 +815,7 @@ var oldClientsLock sync.Mutex // } // // If you want all of your tests in a package to be mocked, just call -// Activate from init(): +// [Activate] from init(): // // func init() { // httpmock.Activate() @@ -825,8 +842,8 @@ func Activate() { } // ActivateNonDefault starts the mock environment with a non-default -// http.Client. This emulates the Activate function, but allows for -// custom clients that do not use http.DefaultTransport +// [*http.Client]. This emulates the [Activate] function, but allows for +// custom clients that do not use [http.DefaultTransport]. // // To enable mocks for a test using a custom client, activate at the // beginning of a test: @@ -850,7 +867,7 @@ func ActivateNonDefault(client *http.Client) { // GetCallCountInfo gets the info on all the calls httpmock has caught // since it was activated or reset. The info is returned as a map of // the calling keys with the number of calls made to them as their -// value. The key is the method, a space, and the url all concatenated +// value. The key is the method, a space, and the URL all concatenated // together. // // As a special case, regexp responders generate 2 entries for each @@ -889,7 +906,7 @@ func GetTotalCallCount() int { // // when this test ends, the mock environment will close // } // -// Since go 1.14 you can also use (*testing.T).Cleanup() method as in: +// Since go 1.14 you can also use [*testing.T.Cleanup] method as in: // // func TestFetchArticles(t *testing.T) { // httpmock.Activate() @@ -926,7 +943,7 @@ func ZeroCallCounters() { } // DeactivateAndReset is just a convenience method for calling -// Deactivate() and then Reset(). +// [Deactivate] and then [Reset]. // // Happy deferring! func DeactivateAndReset() { @@ -951,19 +968,19 @@ func DeactivateAndReset() { // If url begins with "=~", the following chars are considered as a // regular expression. If this regexp can not be compiled, it panics. // Note that the "=~" prefix remains in statistics returned by -// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp +// [GetCallCountInfo]. As 2 regexps can match the same URL, the regexp // responders are tested in the order they are registered. Registering // an already existing regexp responder (same method & same regexp // string) replaces its responder, but does not change its position. // // Registering an already existing responder resets the corresponding -// statistics as returned by GetCallCountInfo(). +// statistics as returned by [GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by [GetCallCountInfo]. It does // nothing if it does not already exist. // -// See RegisterRegexpResponder() to directly pass a *regexp.Regexp. +// See [RegisterRegexpResponder] to directly pass a *regexp.Regexp. // // Example: // @@ -988,7 +1005,7 @@ func DeactivateAndReset() { // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible // mistake. This panic can be disabled by setting -// DefaultTransport.DontCheckMethod to true prior to this call. +// [DefaultTransport].DontCheckMethod to true prior to this call. func RegisterResponder(method, url string, responder Responder) { DefaultTransport.RegisterResponder(method, url, responder) } @@ -1003,16 +1020,16 @@ func RegisterResponder(method, url string, responder Responder) { // tested in the order they are registered. Registering an already // existing regexp responder (same method & same regexp string) // replaces its responder, but does not change its position, and -// resets the corresponding statistics as returned by GetCallCountInfo(). +// resets the corresponding statistics as returned by [GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by [GetCallCountInfo]. It does // nothing if it does not already exist. // // A "=~" prefix is added to the stringified regexp in the statistics -// returned by GetCallCountInfo(). +// returned by [GetCallCountInfo]. // -// See RegisterResponder function and the "=~" prefix in its url +// See [RegisterResponder] function and the "=~" prefix in its url // parameter to avoid compiling the regexp by yourself. // // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD, @@ -1023,29 +1040,29 @@ func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder DefaultTransport.RegisterRegexpResponder(method, urlRegexp, responder) } -// RegisterResponderWithQuery it is same as RegisterResponder, but +// RegisterResponderWithQuery it is same as [RegisterResponder], but // doesn't depends on query items order. // // query type can be: // -// url.Values -// map[string]string -// string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function) +// - [url.Values] +// - map[string]string +// - string, a query string like "a=12&a=13&b=z&c" (see [url.ParseQuery] function) // // If the query type is not recognized or the string cannot be parsed -// using net/url.ParseQuery, a panic() occurs. +// using [url.ParseQuery], a panic() occurs. // -// Unlike RegisterResponder, path cannot be prefixed by "=~" to say it +// Unlike [RegisterResponder], path cannot be prefixed by "=~" to say it // is a regexp. If it is, a panic occurs. // // Registering an already existing responder resets the corresponding -// statistics as returned by GetCallCountInfo(). +// statistics as returned by [GetCallCountInfo]. // -// Registering a nil Responder removes the existing one and the -// corresponding statistics as returned by GetCallCountInfo(). It does +// Registering a nil [Responder] removes the existing one and the +// corresponding statistics as returned by [GetCallCountInfo]. It does // nothing if it does not already exist. // -// Example using a net/url.Values: +// Example using a [url.Values]: // // func TestFetchArticles(t *testing.T) { // httpmock.Activate() @@ -1105,16 +1122,41 @@ func RegisterResponderWithQuery(method, path string, query any, responder Respon DefaultTransport.RegisterResponderWithQuery(method, path, query, responder) } -// RegisterNoResponder adds a mock that is called whenever a request -// for an unregistered URL is received. The default behavior is to -// return a connection error. +// RegisterNoResponder is used to register a responder that is called +// if no other responders are found. The default is [ConnectionFailure] +// that returns a connection error. +// +// Use it in conjunction with [NewNotFoundResponder] to ensure that all +// routes have been mocked: +// +// import ( +// "testing" +// "github.com/jarcoal/httpmock" +// ) +// ... +// func TestMyApp(t *testing.T) { +// ... +// // Calls testing.Fatal with the name of Responder-less route and +// // the stack trace of the call. +// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) +// +// will abort the current test and print something like: +// +// transport_test.go:735: Called from net/http.Get() +// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 +// github.com/jarcoal/httpmock.TestCheckStackTracer() +// at /go/src/testing/testing.go:865 +// testing.tRunner() +// at /go/src/runtime/asm_amd64.s:1337 +// +// If responder is passed as nil, the default behavior +// ([ConnectionFailure]) is re-enabled. // // In some cases you may not want all URLs to be mocked, in which case // you can do this: // // func TestFetchArticles(t *testing.T) { -// httpmock.Activate() -// defer httpmock.DeactivateAndReset() +// ... // httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip) // // // any requests that don't have a registered URL will be fetched normally @@ -1128,9 +1170,10 @@ func RegisterNoResponder(responder Responder) { var ErrSubmatchNotFound = errors.New("submatch not found") // GetSubmatch has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as a -// string. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as a string. Example: // // RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1144,7 +1187,7 @@ var ErrSubmatchNotFound = errors.New("submatch not found") // }) // }) // -// It panics if n < 1. See MustGetSubmatch to avoid testing the +// It panics if n < 1. See [MustGetSubmatch] to avoid testing the // returned error. func GetSubmatch(req *http.Request, n int) (string, error) { if n <= 0 { @@ -1160,9 +1203,10 @@ func GetSubmatch(req *http.Request, n int) (string, error) { } // GetSubmatchAsInt has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as an -// int64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as an int64. Example: // // RegisterResponder("GET", `=~^/item/id/(\d+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1176,7 +1220,7 @@ func GetSubmatch(req *http.Request, n int) (string, error) { // }) // }) // -// It panics if n < 1. See MustGetSubmatchAsInt to avoid testing the +// It panics if n < 1. See [MustGetSubmatchAsInt] to avoid testing the // returned error. func GetSubmatchAsInt(req *http.Request, n int) (int64, error) { sm, err := GetSubmatch(req, n) @@ -1187,9 +1231,10 @@ func GetSubmatchAsInt(req *http.Request, n int) (int64, error) { } // GetSubmatchAsUint has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as a -// uint64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as a uint64. Example: // // RegisterResponder("GET", `=~^/item/id/(\d+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1203,7 +1248,7 @@ func GetSubmatchAsInt(req *http.Request, n int) (int64, error) { // }) // }) // -// It panics if n < 1. See MustGetSubmatchAsUint to avoid testing the +// It panics if n < 1. See [MustGetSubmatchAsUint] to avoid testing the // returned error. func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) { sm, err := GetSubmatch(req, n) @@ -1214,9 +1259,10 @@ func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) { } // GetSubmatchAsFloat has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as a -// float64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as a float64. Example: // // RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1231,7 +1277,7 @@ func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) { // }) // }) // -// It panics if n < 1. See MustGetSubmatchAsFloat to avoid testing the +// It panics if n < 1. See [MustGetSubmatchAsFloat] to avoid testing the // returned error. func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) { sm, err := GetSubmatch(req, n) @@ -1241,11 +1287,12 @@ func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) { return strconv.ParseFloat(sm, 64) } -// MustGetSubmatch works as GetSubmatch except that it panics in case -// of error (submatch not found). It has to be used in Responders -// installed by RegisterRegexpResponder or RegisterResponder + "=~" -// URL prefix. It allows to retrieve the n-th submatch of the matching -// regexp, as a string. Example: +// MustGetSubmatch works as [GetSubmatch] except that it panics in +// case of error (submatch not found). It has to be used in Responders +// installed by [RegisterRegexpResponder] or [RegisterResponder] + +// "=~" URL prefix (as well as [MockTransport.RegisterRegexpResponder] +// or [MockTransport.RegisterResponder]). It allows to retrieve the +// n-th submatch of the matching regexp, as a string. Example: // // RegisterResponder("GET", `=~^/item/name/([^/]+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1265,12 +1312,13 @@ func MustGetSubmatch(req *http.Request, n int) string { return s } -// MustGetSubmatchAsInt works as GetSubmatchAsInt except that it +// MustGetSubmatchAsInt works as [GetSubmatchAsInt] except that it // panics in case of error (submatch not found or invalid int64 // format). It has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as an -// int64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as an int64. Example: // // RegisterResponder("GET", `=~^/item/id/(\d+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1290,12 +1338,13 @@ func MustGetSubmatchAsInt(req *http.Request, n int) int64 { return i } -// MustGetSubmatchAsUint works as GetSubmatchAsUint except that it +// MustGetSubmatchAsUint works as [GetSubmatchAsUint] except that it // panics in case of error (submatch not found or invalid uint64 // format). It has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as a -// uint64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as a uint64. Example: // // RegisterResponder("GET", `=~^/item/id/(\d+)\z`, // func(req *http.Request) (*http.Response, error) { @@ -1315,12 +1364,13 @@ func MustGetSubmatchAsUint(req *http.Request, n int) uint64 { return u } -// MustGetSubmatchAsFloat works as GetSubmatchAsFloat except that it +// MustGetSubmatchAsFloat works as [GetSubmatchAsFloat] except that it // panics in case of error (submatch not found or invalid float64 // format). It has to be used in Responders installed by -// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It -// allows to retrieve the n-th submatch of the matching regexp, as a -// float64. Example: +// [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix +// (as well as [MockTransport.RegisterRegexpResponder] or +// [MockTransport.RegisterResponder]). It allows to retrieve the n-th +// submatch of the matching regexp, as a float64. Example: // // RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`, // func(req *http.Request) (*http.Response, error) {