上一节课我给大家展示了项目Dao层里的CURD操作我们该怎么做单元测试,尤其是Insert、Update这些需要对数据库进行更改的操作怎么用sqlMock的方式让我们既不用真正对数据库发起操作也能验证这些操作是否符合预期。
与数据库的CURD操作类似,当我们对包含API接口调用的代码进行单元测试时,肯定也是希望即不用对接口发起真正的网络请求调用,也能验证我们的API对接程序是否符合预期。那么今天我们就聚焦于怎么为与API对接程序做单元测试,本节大纲如下:
在开发项目的过程中总会遇到要调用依赖方接口的情况,如果依赖方的API接口还没有开发好,通常我们会先约定好API接口的请求参数、响应结构和各类错误对应的响应码。这时双方再按照这个约定同步进行开发。
除了上面说的情况外,还有一种就是当你开发的功能需要与微信支付类的API进行对接时,因为各种订单、签名、证书等的限制你在开发阶段也不能直接去调用支付的API来验证自己开发的程序是否能成功完成对接,这种时候我们该怎么办呢?很多人会说发到测试环境让QA造单子测,很多公司里的项目也确实是这么干的。
针对上面说的这些情况,我们有没有什么办法在开发阶段就能通过单元测试来验证我们写的程序符不符合预期呢?这就需要我们掌握对API调用进行Mock的技巧了。
gock 是 Go 生态下一个提供无侵入 HTTP Mock 的工具,用来在单元测试中Mock API 的调用,即不对要请求的API发起真正的网络调用,而是由gock拦截到请求后返回我们指定的Mock响应。
它支持用请求参数、请求头、请求体等方式设置拦截请求的匹配条件,一旦匹配成功就会拦截测试程序中对API的调用,返回我们提前预设好的响应。
gock 的安装方法如下
go get -u github.com/h2non/gock
关于 gock 的基本使用方法,可以参考我写的这篇文章:用gock 拦截HTTP请求,Mock API调用 。我们接下来直接进入API Mock的实战环节。
我们项目的API对接都放在了API对接层 library 中,实战环节中我挑选了两个API对接逻辑演示如何对他们进行Mock单元测试,它们正好能覆盖了GET、POST两种请求方式下按照请求参数匹配拦截API请求和JSON请求体匹配拦截API请求。
我们项目里的对外API对接都放在library层中,按照上节课我们为项目做的的单元测试目录规划,它的单元测试_test.go 文件都应该放在test/library 目录中。
.
|---test
| |---controller # controller 的测试用例
| |---dao # dao 的测试用例
| |---domainservice # 逻辑层领域服务的测试用例
| |---library # 外部API对接的测试用例
在开始写单元测试前我们还是需要在TestMain方法中做一些 library 包中单元测试的初始化基础工作。
func TestMain(m *testing.M) {
client := &http.Client{Transport: &http.Transport{}}
gock.InterceptClient(client)
// 把框架的httptool使用的http client 换成gock拦截的client
httptool.SetUTHttpClient(client)
os.Exit(m.Run())
}
因为我们项目中的API调用都是httptool来发起的,所以我们需要把 httptool持有的全局httpClient 替换成由 gock 做了拦截的httpClient,只有这样才能为项目中library层中封装的各个API对接程序做拦截和Mock。
实战环节先来一个简单点的案例,在library中我们曾经演示过一个用 whois API 查询本机IP详情的程序,具体程序如下:
func (whois *WhoisLib) GetHostIpDetail() (*WhoisIpDetail, error) {
log := logger.New(whois.ctx)
httpStatusCode, respBody, err := httptool.Get(
whois.ctx, "https://ipwho.is",
httptool.WithHeaders(map[string]string{
"User-Agent": "curl/7.77.0",
}),
)
if err != nil {
log.Error("whois request error", "err", err, "httpStatusCode", httpStatusCode)
returnnil, err
}
reply := new(WhoisIpDetail)
json.Unmarshal(respBody, reply)
return reply, nil
}
里面的逻辑很简单,只有一个简单的对whois API 的GET方式的请求调用,我们对 WhoisLib 的GetHostIpDetail 方法做单测时,可以对whois的API做Mock,让API返回我们指定的IP地址,然后让测试程序验证 GetHostIpDetail 方法返回的是不是这个指定的IP地址。
具体的单元测试方法如下:
func TestWhoisLib_GetHostIpDetail(t *testing.T) {
defer gock.Off()
gock.New("https://ipwho.is").
MatchHeader("User-Agent", "curl/7.77.0").Get("").
Reply(200).
BodyString("{\"ip\":\"127.126.113.220\",\"success\":true}")
ipDetail, err := library.NewWhoisLib(context.TODO()).GetHostIpDetail()
assert.Nil(t, err)
assert.Equal(t, "127.126.113.220", ipDetail.Ip)
}
你可能会说这个例子也太简单了,别着急,接下来我们来个难的。
当在开发的功能需要与微信支付类的API进行对接时,因为各种订单、签名、证书等的限制,在开发阶段不能直接去调用支付的API来验证自己开发的程序是否能成功完成对接,在这种情况下如果能掌握API Mock技巧,能让我们提前做好自己开发程序的逻辑验证。
我们拿项目 WxPayLib 中的 CreateOrderPay 方法来给大家举例子,这个方法会根据订单数据向微信支付的JSAPI发起支付预下单,拿到预下单ID后再生成前端唤起微信进行支付所需要的信息返给前端。
CreateOrderPay 方法的实现如下:
func (wpl *WxPayLib) CreateOrderPay(order *do.Order, userOpenId string) (payInvokeInfo *WxPayInvokeInfo, err error) {
// 创建预支付单
// 微信支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
payDescription := fmt.Sprintf("GOMALL 商场购买%s 等商品", order.Items[0].CommodityName)
prePayPram := &PrePayParam{
AppId: wpl.payConfig.AppId,
MchId: wpl.payConfig.MchId,
Description: payDescription,
OutTradeNo: order.OrderNo,
NotifyUrl: wpl.payConfig.NotifyUrl,
}
prePayPram.Amount.Total = order.PayMoney
prePayPram.Amount.Currency = "CNY"
prePayPram.Payer.OpenId = userOpenId
reqBody, _ := json.Marshal(prePayPram)
token, err := wpl.getToken(http.MethodPost, string(reqBody), prePayApiUrl)
if err != nil {
err = errcode.Wrap("WxPayLibCreatePrePayError", err)
return
}
_, replyBody, err := httptool.Post(wpl.ctx, prePayApiUrl, reqBody, httptool.WithHeaders(map[string]string{
"Authorization": "WECHATPAY2-SHA256-RSA2048 " + token,
}))
if err != nil {
err = errcode.Wrap("WxPayLibCreatePrePayError", err)
return
}
prepayReply := struct {
PrePayId string`json:"prepay_id"`
}{}
if err = json.Unmarshal(replyBody, &prepayReply); err != nil {
err = errcode.Wrap("WxPayLibCreatePrePayError", err)
return
}
// 生成前端调起支付需要的参数
payInvokeInfo, err = wpl.genPayInvokeInfo(prepayReply.PrePayId)
if err != nil {
err = errcode.Wrap("WxPayLibCreatePrePayError", err)
}
return payInvokeInfo, nil
}
观察 CreateOrderPay 中的代码我们发现,方法中除了对微信支付API的请求外 getToken、genPayInvokeInfo 这两个 WxPayLib 中定义的私有方法分别做了拿微信支付请求Token 和生成前端唤起微信客户端进行支付的参数的工作。
那么想要对 CreateOrderPay 进行单元测试除了Mock方法中对微信支付预下单接口的API请求外,还需要Mock 依赖的getToken和genPayInvokeInfo两个方法的返回,而且因为它们两个是私有方法,在test目录Mock 它们就必须使用支持 Mock 私有方法的工具,好在Go的生态够全,这里我使用的是gomonkey这个库
gomonkey 的使用方法请参考:Go代码测试时怎么打桩?给大家写了几个常用案例
完成这个测试程序中主要分三步
《Go项目搭建和整洁开发实战》专栏分为五大部分,重点章节如下
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有