- Notifications
You must be signed in to change notification settings - Fork110
HTTP traffic mocking and testing made easy in Go ༼ʘ̚ل͜ʘ̚༽
License
h2non/gock
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Versatile HTTP mocking made easy inGo that works with anynet/http
based stdlib implementation.
Heavily inspired bynock.There is also its Python port,pook.
To get started, take a look to theexamples.
- Simple, expressive, fluent API.
- Semantic API DSL for declarative HTTP mock declarations.
- Built-in helpers for easy JSON/XML mocking.
- Supports persistent and volatile TTL-limited mocks.
- Full regular expressions capable HTTP request mock matching.
- Designed for both testing and runtime scenarios.
- Match request by method, URL params, headers and bodies.
- Extensible and pluggable HTTP matching rules.
- Ability to switch between mock and real networking modes.
- Ability to filter/map HTTP requests for accurate mock matching.
- Supports map and filters to handle mocks easily.
- Wide compatible HTTP interceptor using
http.RoundTripper
interface. - Works with any
net/http
compatible client, such asgentleman. - Network timeout/cancelation delay simulation.
- Extensible and hackable API.
- Dependency free.
go get -u github.com/h2non/gock
Seegodoc reference for detailed API documentation.
- Intercepts any HTTP outgoing request via
http.DefaultTransport
or customhttp.Transport
used by anyhttp.Client
. - Matches outgoing HTTP requests against a pool of defined HTTP mock expectations in FIFO declaration order.
- If at least one mock matches, it will be used in order to compose the mock HTTP response.
- If no mock can be matched, it will resolve the request with an error, unless real networking mode is enable, in which case a real HTTP request will be performed.
Declare your mocks before you start declaring the concrete test logic:
funcTestFoo(t*testing.T) {defergock.Off()// Flush pending mocks after test executiongock.New("http://server.com").Get("/bar").Reply(200).JSON(map[string]string{"foo":"bar"})// Your test code starts here...}
If you're running concurrent code, be aware that your mocks are declared first to avoid unexpectedrace conditions while configuringgock
or intercepting custom HTTP clients.
gock
is not fully thread-safe, but sensible parts are.Any help makinggock
more reliable in this sense is appreciated.
If you're mocking a bunch of mocks in the same test suite, it's recommended to define the moreconcrete mocks first, and then the generic ones.
This approach usually avoids matching unexpected generic mocks (e.g: specific header, body payload...) instead of the generic ones that performs less complex matches.
In other to minimize potential side effects within your test code, it's a good practicedisablinggock
once you are done with your HTTP testing logic.
A Go idiomatic approach for doing this can be using it in adefer
statement, such as:
funcTestGock (t*testing.T) {defergock.Off()// ... my test code goes here}
You don't need to intercept multiple times the samehttp.Client
instance.
Just callgock.InterceptClient(client)
once, typically at the beginning of your test scenarios.
NOTE: this is not required is you are usinghttp.DefaultClient
orhttp.DefaultTransport
.
As a good testing pattern, you should callgock.RestoreClient(client)
after running your test scenario, typically as after clean up hook.
You can also use adefer
statement for doing it, as you do withgock.Off()
, such as:
funcTestGock (t*testing.T) {defergock.Off()defergock.RestoreClient(client)// ... my test code goes here}
Seeexamples directory for more featured use cases.
package testimport ("io/ioutil""net/http""testing""github.com/nbio/st""github.com/h2non/gock")funcTestSimple(t*testing.T) {defergock.Off()gock.New("http://foo.com").Get("/bar").Reply(200).JSON(map[string]string{"foo":"bar"})res,err:=http.Get("http://foo.com/bar")st.Expect(t,err,nil)st.Expect(t,res.StatusCode,200)body,_:=ioutil.ReadAll(res.Body)st.Expect(t,string(body)[:13],`{"foo":"bar"}`)// Verify that we don't have pending mocksst.Expect(t,gock.IsDone(),true)}
package testimport ("io/ioutil""net/http""testing""github.com/nbio/st""github.com/h2non/gock")funcTestMatchHeaders(t*testing.T) {defergock.Off()gock.New("http://foo.com").MatchHeader("Authorization","^foo bar$").MatchHeader("API","1.[0-9]+").HeaderPresent("Accept").Reply(200).BodyString("foo foo")req,err:=http.NewRequest("GET","http://foo.com",nil)req.Header.Set("Authorization","foo bar")req.Header.Set("API","1.0")req.Header.Set("Accept","text/plain")res,err:= (&http.Client{}).Do(req)st.Expect(t,err,nil)st.Expect(t,res.StatusCode,200)body,_:=ioutil.ReadAll(res.Body)st.Expect(t,string(body),"foo foo")// Verify that we don't have pending mocksst.Expect(t,gock.IsDone(),true)}
package testimport ("io/ioutil""net/http""testing""github.com/nbio/st""github.com/h2non/gock")funcTestMatchParams(t*testing.T) {defergock.Off()gock.New("http://foo.com").MatchParam("page","1").MatchParam("per_page","10").Reply(200).BodyString("foo foo")req,err:=http.NewRequest("GET","http://foo.com?page=1&per_page=10",nil)res,err:= (&http.Client{}).Do(req)st.Expect(t,err,nil)st.Expect(t,res.StatusCode,200)body,_:=ioutil.ReadAll(res.Body)st.Expect(t,string(body),"foo foo")// Verify that we don't have pending mocksst.Expect(t,gock.IsDone(),true)}
package testimport ("bytes""io/ioutil""net/http""testing""github.com/nbio/st""github.com/h2non/gock")funcTestMockSimple(t*testing.T) {defergock.Off()gock.New("http://foo.com").Post("/bar").MatchType("json").JSON(map[string]string{"foo":"bar"}).Reply(201).JSON(map[string]string{"bar":"foo"})body:=bytes.NewBuffer([]byte(`{"foo":"bar"}`))res,err:=http.Post("http://foo.com/bar","application/json",body)st.Expect(t,err,nil)st.Expect(t,res.StatusCode,201)resBody,_:=ioutil.ReadAll(res.Body)st.Expect(t,string(resBody)[:13],`{"bar":"foo"}`)// Verify that we don't have pending mocksst.Expect(t,gock.IsDone(),true)}
package testimport ("io/ioutil""net/http""testing""github.com/nbio/st""github.com/h2non/gock")funcTestClient(t*testing.T) {defergock.Off()gock.New("http://foo.com").Reply(200).BodyString("foo foo")req,err:=http.NewRequest("GET","http://foo.com",nil)client:=&http.Client{Transport:&http.Transport{}}gock.InterceptClient(client)res,err:=client.Do(req)st.Expect(t,err,nil)st.Expect(t,res.StatusCode,200)body,_:=ioutil.ReadAll(res.Body)st.Expect(t,string(body),"foo foo")// Verify that we don't have pending mocksst.Expect(t,gock.IsDone(),true)}
package mainimport ("fmt""io/ioutil""net/http""github.com/h2non/gock")funcmain() {defergock.Off()defergock.DisableNetworking()gock.EnableNetworking()gock.New("http://httpbin.org").Get("/get").Reply(201).SetHeader("Server","gock")res,err:=http.Get("http://httpbin.org/get")iferr!=nil {fmt.Errorf("Error: %s",err) }// The response status comes from the mockfmt.Printf("Status: %d\n",res.StatusCode)// The server header comes from mock as wellfmt.Printf("Server header: %s\n",res.Header.Get("Server"))// Response body is the originalbody,_:=ioutil.ReadAll(res.Body)fmt.Printf("Body: %s",string(body))}
package mainimport ("bytes""net/http""github.com/h2non/gock")funcmain() {defergock.Off()gock.Observe(gock.DumpRequest)gock.New("http://foo.com").Post("/bar").MatchType("json").JSON(map[string]string{"foo":"bar"}).Reply(200)body:=bytes.NewBuffer([]byte(`{"foo":"bar"}`))http.Post("http://foo.com/bar","application/json",body)}
You can easily hackgock
defining custom matcher functions with own matching rules.
Seeadd matcher functions andcustom matching layer examples for further details.
MIT - Tomas Aparicio
About
HTTP traffic mocking and testing made easy in Go ༼ʘ̚ل͜ʘ̚༽