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

我们在开发项目的过程中总会遇到要调用依赖方接口的情况,如果依赖方的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 的安装方法如下

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

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

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 请求体匹配

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 拦截到。

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了程序中用到的项目对接层的私有方法

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

现在扫码订阅专栏,加入专栏和大家一起学习吧

a1f6c5c4b3e0a9177e91f6c7e1981135.png 本专栏分为五大部分,大部分内容已经更新完成 990231303fc623987b82d74b28876724.png

扫描上方二维码或者访问 https://xiaobot.net/p/golang 即刻订阅

想更详细地了解专栏内容,或者希望专栏有优惠时能提前告知你,都可以添加下面我的微信

d5e375fdb27c44a13ae8dab56fbf63c1.jpeg
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值