golang轻松实现HTTP接口模拟测试的插件库gock的使用

Golang轻松实现HTTP接口模拟测试的插件库gock的使用

gock是一个功能强大的Go语言HTTP模拟测试库,它可以轻松地模拟任何基于net/http标准库实现的HTTP请求。

特性

  • 简单、表达力强的流式API
  • 支持JSON/XML模拟的内置助手
  • 支持持久性和临时TTL限制的模拟
  • 完整的正则表达式HTTP请求匹配能力
  • 支持通过方法、URL参数、headers和body匹配请求
  • 可扩展和可插拔的HTTP匹配规则
  • 能够在模拟和真实网络模式之间切换
  • 兼容任何net/http兼容的客户端

安装

go get -u github.com/h2non/gock

基本使用示例

简单测试模拟

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://server.com").
    Get("/bar").
    Reply(200).
    JSON(map[string]string{"foo": "bar"})

  res, err := http.Get("http://server.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"}`)

  // 验证没有挂起的模拟
  st.Expect(t, gock.IsDone(), true)
}

请求头匹配

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")

  // 验证没有挂起的模拟
  st.Expect(t, gock.IsDone(), true)
}

请求参数匹配

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")

  // 验证没有挂起的模拟
  st.Expect(t, gock.IsDone(), true)
}

JSON body匹配和响应

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"}`)

  // 验证没有挂起的模拟
  st.Expect(t, gock.IsDone(), true)
}

模拟自定义http.Client和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")

  // 验证没有挂起的模拟
  st.Expect(t, gock.IsDone(), true)
}

启用真实网络

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)
  }

  // 响应状态来自模拟
  fmt.Printf("Status: %d\n", res.StatusCode)
  // 服务器头也来自模拟
  fmt.Printf("Server header: %s\n", res.Header.Get("Server"))
  // 响应体是原始的
  body, _ := ioutil.ReadAll(res.Body)
  fmt.Printf("Body: %s", string(body))
}

调试拦截的HTTP请求

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)
}

使用技巧

  1. 在测试开始时声明模拟
  2. 使用defer gock.Off()确保测试完成后清理模拟
  3. 对于并发代码,确保先声明模拟以避免竞态条件
  4. 先定义更复杂的模拟,再定义通用的
  5. 拦截自定义http.Client只需一次
  6. 拦截后恢复http.Client

许可证

MIT - Tomas Aparicio


更多关于golang轻松实现HTTP接口模拟测试的插件库gock的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang轻松实现HTTP接口模拟测试的插件库gock的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用gock实现Golang HTTP接口模拟测试

gock是一个强大的Go语言HTTP模拟库,可以轻松拦截和模拟HTTP请求,非常适合单元测试和API开发。下面我将详细介绍gock的使用方法。

安装gock

go get -u gopkg.in/h2non/gock.v1

基本使用

1. 简单请求模拟

package main

import (
	"fmt"
	"net/http"

	"gopkg.in/h2non/gock.v1"
)

func main() {
	defer gock.Off() // 确保测试结束后关闭mock

	// 设置mock
	gock.New("http://api.example.com").
		Get("/users").
		Reply(200).
		JSON(map[string]string{"name": "John"})

	// 发起请求
	res, err := http.Get("http://api.example.com/users")
	if err != nil {
		panic(err)
	}

	fmt.Println("Status:", res.Status)
	// 处理响应...
}

2. 带参数的请求匹配

gock.New("http://api.example.com").
    Post("/login").
    MatchHeader("Content-Type", "^application/json$").
    MatchParam("token", "^abc123$").
    JSON(map[string]string{"username": "admin"}).
    Reply(200).
    JSON(map[string]string{"token": "xyz789"})

3. 动态响应

gock.New("http://api.example.com").
    Get("/dynamic").
    Reply(201).
    SetHeader("Server", "gock").
    JSON(func() map[string]string {
        return map[string]string{
            "id":   "123",
            "time": time.Now().String(),
        }
    })

测试示例

单元测试中使用gock

package main

import (
	"net/http"
	"testing"

	"gopkg.in/h2non/gock.v1"
)

func TestGetUser(t *testing.T) {
	defer gock.Off() // 测试完成后清理

	// 设置mock
	gock.New("http://api.example.com").
		Get("/users/123").
		Reply(200).
		JSON(map[string]string{"id": "123", "name": "John"})

	// 执行测试
	res, err := http.Get("http://api.example.com/users/123")
	if err != nil {
		t.Fatalf("请求失败: %v", err)
	}

	if res.StatusCode != 200 {
		t.Errorf("预期状态码200, 实际得到 %d", res.StatusCode)
	}

	// 验证所有mock是否被调用
	if !gock.IsDone() {
		t.Errorf("未完成所有预期的HTTP请求")
	}
}

高级功能

1. 持久化mock

// 持久化mock,不会被Off()清除
gock.New("http://api.example.com").
    Get("/persistent").
    Persist().
    Reply(200).
    BodyString("persistent response")

2. 网络延迟模拟

gock.New("http://api.example.com").
    Get("/slow").
    Delay(2 * time.Second). // 2秒延迟
    Reply(200)

3. 错误模拟

gock.New("http://api.example.com").
    Get("/error").
    ReplyError("connection refused")

4. 自定义匹配器

gock.New("http://api.example.com").
    Post("/custom").
    AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) {
        return req.Header.Get("X-Custom") == "123", nil
    }).
    Reply(200)

最佳实践

  1. 及时清理:始终使用defer gock.Off()确保测试完成后清理mock
  2. 验证调用:使用gock.IsDone()验证所有预期的请求都已被调用
  3. 组织mock:将相关的mock组织在一起提高可读性
  4. 避免过度mock:只mock必要的请求,保持测试的真实性

gock是Go语言中进行HTTP测试的强大工具,它简单易用但功能丰富,能够满足各种HTTP接口测试需求。通过合理使用gock,可以显著提高测试的可靠性和执行速度。

回到顶部