前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Go项目里的API对接,这样做Mock测试才舒服

Go项目里的API对接,这样做Mock测试才舒服

作者头像
KevinYan
发布2025-02-06 20:18:22
发布2025-02-06 20:18:22
7200
代码可运行
举报
文章被收录于专栏:网管叨bi叨网管叨bi叨
运行总次数:0
代码可运行

我们在开发项目的过程中总会遇到要调用依赖方接口的情况,如果依赖方的API接口还没有开发好,通常我们会先约定好API接口的请求参数、响应结构和各类错误对应的响应码,再按照约定好请求和响应进行开发。

除了上面说的情况外,还有一种就是当你开发的功能需要与微信支付类的API进行对接时,因为各种订单、签名、证书等的限制你在开发阶段也不能直接去调用支付的API来验证自己开发的程序是否能成功完成对接,这种时候我们该怎么办呢?很多人会说发到测试环节让QA造单子测,很多公司里的项目也确实是这么干的。

针对上面说的两种情况,我们有没有什么办法在开发阶段就能通过单元测试来验证我们写的程序符不符合预期呢?这就需要我们掌握对API调用进行Mock的技巧了。

gock

gock 是 Go 生态下一个提供无侵入 HTTP Mock 的工具,用来在单元测试中Mock API 的调用,即不对要请求的API发起真正的调用,而是由gock拦截到请求后返回我们指定的Mock响应。

它是如何模拟的

  • 用 http.DefaultTransport或自定义http.Transport拦截的任何 HTTP 请求流量
  • 将传出的 HTTP 请求与按 FIFO 声明顺序定义的 HTTP 模拟期望池匹配。
  • 如果至少有一个模拟匹配,它将被用来组成模拟 HTTP 响应。
  • 如果没有匹配到的mock,则解析请求报错,除非启用了真实网络模式,在这种情况下,将执行真实的HTTP请求。

gock 的安装方法

gock 的安装方法如下

代码语言:javascript
代码运行次数:0
复制
go get -u github.com/h2non/gock

gock 在官方的Github中给出了一些使用例子

  • 官方GitHub:https://github.com/h2non/gock
  • 官方给出的例子:https://github.com/h2non/gock/tree/master/_examples

这里我找一些典型常用的案例分享给大家,也说一下我在使用后对它们的理解,让大家能更容易上手。

gock 的使用案例

匹配请求头,对匹配到的请求进行Mock

代码语言:javascript
代码运行次数:0
复制
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)
}

请求参数匹配,对匹配到的请求进行Mock

代码语言:javascript
代码运行次数:0
复制
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 请求体匹配

代码语言:javascript
代码运行次数:0
复制
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)
}

上面JSON的请求体要跟调用时发送的请求体完全一致,不然gock匹配不到这个请求, 如果匹配不上会报错:gock: cannot match any request。

上面的这些案例都是用的Go http 的 default client,通常在项目里会自己封装 http util 来简化和标准化项目的API请求调用 ,这时候需要把 http util里的client 替换成经过 gock.InterceptClient(client) 拦截的Client ,这样用http util 发起的API请求才能gock 拦截到。

代码语言:javascript
代码运行次数:0
复制
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)
}

微信支付API对接怎么Mock测试

因为各种订单、签名、证书等的限制你在开发阶段不能直接去调用支付的API来验证自己开发的程序是否能成功完成对接。

我在《Go项目搭建和整洁开发实战》的单元测试实战部分,给跟微信支付API对接的程序做了单元测试,除了使用到gock外,还用gomonkey mock了程序中用到的项目对接层的私有方法

代码语言:javascript
代码运行次数:0
复制
func TestWxPayLib_CreateOrderPay(t *testing.T) {
 defer gock.Off()
 ......
 request := library.PrePayParam{
  AppId:       payConfig.AppId,
  MchId:       payConfig.MchId,
  OutTradeNo:  order.OrderNo,
  NotifyUrl:   payConfig.NotifyUrl,
  Amount: ...
  Payer: struct {
   OpenId string `json:"open_id"`
  }{OpenId: openId},
 }

 gock.New("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi").
  Post("").MatchType("json").
  JSON(request).
  Reply(200).
  JSON(map[string]string{"prepay_id": "wx26112221580621e9b071c00d9e093b0000"})

 wxPayLib := library.NewWxPayLib(context.TODO(), payConfig)
 var s *library.WxPayLib
 patchesOne := gomonkey.ApplyPrivateMethod(s, "getToken", func(_ *library.WxPayLib, httpMethod string, requestBody string, wxApiUrl string) (string, error) {
  token := fmt.Sprintf("mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
   payConfig.MchId, "abcddef", time.Now().Unix(), payConfig.PrivateSerialNo, "")
  return token, nil
 })
   ...

 payInfo, err := wxPayLib.CreateOrderPay(order, openId)
 assert.Nil(t, err)
 assert.Equal(t, "e61463f8efa94090b1f366cccfbbb444", payInfo.NonceStr)
 if payInfo.PaySign == "" || payInfo.Package == "" {
  t.Fail()
 }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 网管叨bi叨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • gock
    • gock 的安装方法
    • gock 的使用案例
  • 微信支付API对接怎么Mock测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档