Project Icon

gock

Go语言HTTP模拟库,简单强大的测试工具

gock是一个灵活的Go语言HTTP模拟库,为net/http相关开发提供支持。它具有简洁的API设计,可进行JSON/XML模拟、请求匹配、网络延迟模拟等。gock适用于测试和实际运行环境,能够灵活切换模拟和真实网络模式。该库与所有net/http兼容客户端无缝集成,是Go开发者进行HTTP交互测试的有力工具。

gock GitHub release GoDoc Coverage Status Go Report Card license

Versatile HTTP mocking made easy in Go that works with any net/http based stdlib implementation.

Heavily inspired by nock. There is also its Python port, pook.

To get started, take a look to the examples.

Features

  • 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 as gentleman.
  • Network timeout/cancelation delay simulation.
  • Extensible and hackable API.
  • Dependency free.

Installation

go get -u github.com/h2non/gock

API

See godoc reference for detailed API documentation.

How it mocks

  1. Intercepts any HTTP outgoing request via http.DefaultTransport or custom http.Transport used by any http.Client.
  2. Matches outgoing HTTP requests against a pool of defined HTTP mock expectations in FIFO declaration order.
  3. If at least one mock matches, it will be used in order to compose the mock HTTP response.
  4. 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.

Tips

Testing

Declare your mocks before you start declaring the concrete test logic:

func TestFoo(t *testing.T) {
  defer gock.Off() // Flush pending mocks after test execution

  gock.New("http://server.com").
    Get("/bar").
    Reply(200).
    JSON(map[string]string{"foo": "bar"})

  // Your test code starts here...
}

Race conditions

If you're running concurrent code, be aware that your mocks are declared first to avoid unexpected race conditions while configuring gock or intercepting custom HTTP clients.

gock is not fully thread-safe, but sensible parts are. Any help making gock more reliable in this sense is appreciated.

Define complex mocks first

If you're mocking a bunch of mocks in the same test suite, it's recommended to define the more concrete 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.

Disable gock traffic interception once done

In other to minimize potential side effects within your test code, it's a good practice disabling gock once you are done with your HTTP testing logic.

A Go idiomatic approach for doing this can be using it in a defer statement, such as:

func TestGock (t *testing.T) {
	defer gock.Off()

	// ... my test code goes here
}

Intercept an http.Client just once

You don't need to intercept multiple times the same http.Client instance.

Just call gock.InterceptClient(client) once, typically at the beginning of your test scenarios.

Restore an http.Client after interception

NOTE: this is not required is you are using http.DefaultClient or http.DefaultTransport.

As a good testing pattern, you should call gock.RestoreClient(client) after running your test scenario, typically as after clean up hook.

You can also use a defer statement for doing it, as you do with gock.Off(), such as:

func TestGock (t *testing.T) {
	defer gock.Off()
	defer gock.RestoreClient(client)

	// ... my test code goes here
}

Examples

See examples directory for more featured use cases.

Simple mocking via tests

package test

import (
  "io/ioutil"
  "net/http"
  "testing"

  "github.com/nbio/st"
  "github.com/h2non/gock"
)

func TestSimple(t *testing.T) {
  defer gock.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 mocks
  st.Expect(t, gock.IsDone(), true)
}

Request headers matching

package test

import (
  "io/ioutil"
  "net/http"
  "testing"

  "github.com/nbio/st"
  "github.com/h2non/gock"
)

func TestMatchHeaders(t *testing.T) {
  defer gock.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 mocks
  st.Expect(t, gock.IsDone(), true)
}

Request param matching

package test

import (
  "io/ioutil"
  "net/http"
  "testing"

  "github.com/nbio/st"
  "github.com/h2non/gock"
)

func TestMatchParams(t *testing.T) {
  defer gock.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 mocks
  st.Expect(t, gock.IsDone(), true)
}

JSON body matching and response

package test

import (
  "bytes"
  "io/ioutil"
  "net/http"
  "testing"
	
	"github.com/nbio/st"
  "github.com/h2non/gock"
)

func TestMockSimple(t *testing.T) {
  defer gock.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 mocks
  st.Expect(t, gock.IsDone(), true)
}

Mocking a custom http.Client and http.RoundTripper

package test

import (
  "io/ioutil"
  "net/http"
  "testing"

  "github.com/nbio/st"
  "github.com/h2non/gock"
)

func TestClient(t *testing.T) {
  defer gock.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 mocks
  st.Expect(t, gock.IsDone(), true)
}

Enable real networking

package main

import (
  "fmt"
  "io/ioutil"
  "net/http"

  "github.com/h2non/gock"
)

func main() {
  defer gock.Off()
  defer gock.DisableNetworking()

  gock.EnableNetworking()
  gock.New("http://httpbin.org").
    Get("/get").
    Reply(201).
    SetHeader("Server", "gock")

  res, err := http.Get("http://httpbin.org/get")
  if err != nil {
    fmt.Errorf("Error: %s", err)
  }

  // The response status comes from the mock
  fmt.Printf("Status: %d\n", res.StatusCode)
  // The server header comes from mock as well
  fmt.Printf("Server header: %s\n", res.Header.Get("Server"))
  // Response body is the original
  body, _ := ioutil.ReadAll(res.Body)
  fmt.Printf("Body: %s", string(body))
}

Debug intercepted http requests

package main

import (
	"bytes"
	"net/http"
	
  "github.com/h2non/gock"
)

func main() {
	defer gock.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)
}

Hacking it!

You can easily hack gock defining custom matcher functions with own matching rules.

See add matcher functions and custom matching layer examples for further details.

License

MIT - Tomas Aparicio

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

白日梦AI

白日梦AI提供专注于AI视频生成的多样化功能,包括文生视频、动态画面和形象生成等,帮助用户快速上手,创造专业级内容。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

讯飞绘镜

讯飞绘镜是一个支持从创意到完整视频创作的智能平台,用户可以快速生成视频素材并创作独特的音乐视频和故事。平台提供多样化的主题和精选作品,帮助用户探索创意灵感。

Project Cover

讯飞文书

讯飞文书依托讯飞星火大模型,为文书写作者提供从素材筹备到稿件撰写及审稿的全程支持。通过录音智记和以稿写稿等功能,满足事务性工作的高频需求,帮助撰稿人节省精力,提高效率,优化工作与生活。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号