• 【gock】 无侵入的 Http Mock 工具


    在这里插入图片描述

    一、前言

    笔者最近和应该是抄了单测老家,各种mock、打桩场景都遇到很多,近期就遇到了一个需要httpmock的场景,主要是在一个环境中有几个依赖服务应为一些原因没法提供,但是有需要再这个环境上来验证一些其他的服务组件是否正常。这种情况一般有几种解决方式,最典型的就是测试做个mock服务,但由于诸多问题这个任务落到了研发这边,我们就在思考怎么来做这个事情,大致想到了一下几种方案:

    • 单独写个http-service提供mock能力,修改服务发现地址
    • 在程序启动时启动一个http端口提供mock服务,在改写服务请求地址
    • 在逻辑层进行判断读取相关返回参数
    • 还有一个就是我提出的通过 gock 无侵入来mock

    首先我们排除了方案3,太过于侵入逻辑层,并且也不够灵活对于各种延迟网络请求失败场景无法模拟,第二个我们排除了方案一,要再去管理一个mock的服务开一套git仓库也有些得不偿失,最终我们在方案二和方案四之前进行选择,最终gock胜出,应为它够工具化,启动、加载、热更新、事件触发等等都可以通过一个配置文件来实现,自己洗一套http服务来实现模块还要设计各类的场景和配置。然后就有了今天的软件分享,来和大家一起聊聊gock这个无侵入的http mock 工具吧。

    PS1:除了上述场景对于本地开发依赖三方的http接口,比如发送邮件发送短信联调需要判断异常流程,编写单测用例对三方一来场景进行模拟等都非常的适合
    PS2:gock的实现原理需要基于 net/http 包或以 net/http 包作为底层的 http-client,比如底层完成独立的fasthttp就没法实现mock的效果

    参考资料

    二、如何使用

    我们可以看见下面这个examples,对https://test.com/api/v1/users 进行了mock,并且通过NetworkingFilter过滤非test.com域名的方式让baidu.com这种还是访问真实流量,并且还能通过Filter来触发mock过程中的time.sleep或者是启动一个goroutine来进行异步逻辑触发等

    package examples
    
    import (
    	"fmt"
    	"net/http"
    	"testing"
    
    	"gopkg.in/h2non/gock.v1"
    )
    
    func Test_HTTPMock(t *testing.T) {
    	defer gock.Off()
    	defer gock.DisableNetworking()
    	gock.EnableNetworking() // 开启真实流量模式,否则所有的http请求都会被mock
    
    	// 配置适应自身业务的拦截规则
    	gock.NetworkingFilter(func(request *http.Request) bool {
    		fmt.Println(request.Host)
    		return request.Host != "test.com"
    	})
    
    	// 设定mock域名路由和返回信息
    	gock.New("https://test.com").Get("/api/v1/users").
    		Persist().
    		Reply(200).
    		JSON(map[string][]string{"mock": {"123456"}})
    
    	// 被mock拦截
    	resp, err := http.Get("https://test.com/api/v1/users")
    	fmt.Println(resp)
    	fmt.Println(err)
    
    	// 正常请求
    	resp, err = http.Get("https://baidu.com")
    	fmt.Println(resp)
    	fmt.Println(err)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    更多的例子:

    请求头匹配

    package test
    
    import (
      "github.com/nbio/st"
      "gopkg.in/h2non/gock.v1"
      "io/ioutil"
      "net/http"
      "testing"
    )
    
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    请求参数匹配

    package test
    
    import (
      "github.com/nbio/st"
      "gopkg.in/h2non/gock.v1"
      "io/ioutil"
      "net/http"
      "testing"
    )
    
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    JSON 正文匹配和响应

    package test
    
    import (
      "bytes"
      "github.com/nbio/st"
      "gopkg.in/h2non/gock.v1"
      "io/ioutil"
      "net/http"
      "testing"
    )
    
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    添加匹配器函数

    package main
    
    import (
    	"fmt"
    	"net/http"
    
    	"gopkg.in/h2non/gock.v1"
    )
    
    func main() {
    	defer gock.Off()
    
    	gock.New("http://httpbin.org").
    		Get("/").
    		AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { return req.URL.Scheme == "http", nil }).
    		AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) { return req.Method == ereq.Method, nil }).
    		Reply(204).
    		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"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    自定义匹配层

    package main
    
    import (
    	"fmt"
    	"gopkg.in/h2non/gock.v1"
    	"net/http"
    )
    
    func main() {
    	defer gock.Off()
    
    	// Create a new custom matcher with HTTP headers only matchers
    	matcher := gock.NewBasicMatcher()
    
    	// Add a custom match function
    	matcher.Add(func(req *http.Request, ereq *gock.Request) (bool, error) {
    		return req.URL.Scheme == "http", nil
    	})
    
    	// Define the mock
    	gock.New("http://httpbin.org").
    		SetMatcher(matcher).
    		Get("/").
    		Reply(204).
    		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"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    三、它有哪些功能&原理是什么?

    特征

    • 简单、富有表现力、流畅的API。
    • 用于声明性HTTP模拟声明的语义API DSL。
    • 内置帮助程序,便于JSON/XML模拟。
    • 支持持久性和易失性TTL限制模拟。
    • 支持完全正则表达式的HTTP请求模拟匹配。
    • 设计用于测试和运行时场景。
    • 按方法、URL参数、标头和正文匹配请求。
    • 可扩展和可插入的HTTP匹配规则。
    • 能够在模拟和真实网络模式之间切换。
    • 能够过滤/映射HTTP请求以实现精确的模拟匹配。
    • 支持映射和过滤器以轻松处理模拟。
    • 使用HTTP的广泛兼容HTTP侦听器。往返器接口。
    • 适用于任何与net/http兼容的客户端,例如Gentler。
    • 网络超时/取消延迟模拟。
    • 可扩展且可黑客攻击的API。
    • 无依赖性。

    它是如何模拟的

    • http.DefaultTransport或自定义http.Transport拦截的任何 HTTP 请求流量
    • 将传出的 HTTP 请求与按 FIFO 声明顺序定义的 HTTP 模拟期望池匹配。
    • 如果至少有一个模拟匹配,它将被用来组成模拟 HTTP 响应。
    • 如果没有匹配到的mock,则解析请求报错,除非启用了真实网络模式,在这种情况下,将执行真实的HTTP请求。
  • 相关阅读:
    Linux修改远程登陆端口
    Rust个人学习笔记2
    简述快速失败(fail-fast)和安全失败(fail-safe)的区别 ?
    【Vue】Vuex 用法案例(一篇足以)
    我的家乡潍坊HTML静态网页 学生个人网页设计作品 学生家乡网页模板 简单个人主页成品
    使用Weka进行数据挖掘与机器学习
    helm部署etcd集群
    Springcloud服务调用Feign组件以及负载均衡
    arm实验
    [附源码]java毕业设计球队管理系统论文
  • 原文地址:https://blog.csdn.net/u011142688/article/details/126082691