Go 单元测试之HTTP请求与API测试

文章目录

    • 一、httptest
      • 1.1 前置代码准备
      • 1.2 介绍
      • 1.3 基本用法
    • 二、gock
      • 2.1介绍
      • 2.2 安装
      • 2.3 基本使用
      • 2.4 举个例子
        • 2.4.1 前置代码
        • 2.4.2 测试用例

一、httptest

1.1 前置代码准备

假设我们的业务逻辑是搭建一个http server端,对外提供HTTP服务。用来处理用户登录请求,用户需要输入邮箱,密码。

package mainimport (regexp "github.com/dlclark/regexp2""github.com/gin-gonic/gin""net/http"
)type UserHandler struct {emailExp    *regexp.RegexppasswordExp *regexp.Regexp
}func (u *UserHandler) RegisterRoutes(server *gin.Engine) {ug := server.Group("/user")ug.POST("/login", u.Login)
}
func NewUserHandler() *UserHandler {const (emailRegexPattern    = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`)emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)return &UserHandler{emailExp:    emailExp,passwordExp: passwordExp,}
}type LoginRequest struct {Email string `json:"email"`Pwd   string `json:"pwd"`
}func (u *UserHandler) Login(ctx *gin.Context) {var req LoginRequestif err := ctx.ShouldBindJSON(&req); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "参数不正确!"})return}// 校验邮箱和密码是否为空if req.Email == "" || req.Pwd == "" {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱或密码不能为空"})return}// 正则校验邮箱ok, err := u.emailExp.MatchString(req.Email)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系统错误!"})return}if !ok {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱格式不正确"})return}// 校验密码格式ok, err = u.passwordExp.MatchString(req.Pwd)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系统错误!"})return}if !ok {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "密码必须大于8位,包含数字、特殊字符"})return}// 校验邮箱和密码是否匹配特定的值来确定登录成功与否if req.Email != "123@qq.com" || req.Pwd != "hello#world123" {ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱或密码不匹配!"})return}ctx.JSON(http.StatusOK, gin.H{"msg": "登录成功!"})
}func InitWebServer(userHandler *UserHandler) *gin.Engine {server := gin.Default()userHandler.RegisterRoutes(server)return server
}func main() {uh := &UserHandler{}server := InitWebServer(uh)server.Run(":8080") // 在8080端口启动服务器
}

1.2 介绍

在 Web 开发场景下,单元测试经常需要模拟 HTTP 请求和响应。使用 httptest 可以让我们在测试代码中创建一个 HTTP 服务器实例,并定义特定的请求和响应行为,从而模拟真实世界的网络交互,在Go语言中,一般都推荐使用Go标准库 net/http/httptest 进行测试。

1.3 基本用法

使用 httptest 的基本步骤如下:

  1. 导入 net/http/httptest 包。
  2. 创建一个 httptest.Server 实例,并指定你想要的服务器行为。
  3. 在测试代码中使用 httptest.NewRequest 创建一个模拟的 HTTP 请求,并将其发送到 httptest.Server
  4. 检查响应内容或状态码是否符合预期。

以下是一个简单的 httptest 用法示例

package mainimport ("bytes""github.com/gin-gonic/gin""github.com/stretchr/testify/assert""net/http""net/http/httptest""testing"
)func TestUserHandler_Login(t *testing.T) {// 定义测试用例testCases := []struct {name     stringreqBody  stringwantCode intwantBody string}{{name:     "登录成功",reqBody:  `{"email": "123@qq.com", "pwd": "hello#world123"}`,wantCode: http.StatusOK,wantBody: `{"msg": "登录成功!"}`,},{name:     "参数不正确",reqBody:  `{"email": "123@qq.com", "pwd": "hello#world123",}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "参数不正确!"}`,},{name:     "邮箱或密码为空",reqBody:  `{"email": "", "pwd": ""}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "邮箱或密码不能为空"}`,},{name:     "邮箱格式不正确",reqBody:  `{"email": "invalidemail", "pwd": "hello#world123"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "邮箱格式不正确"}`,},{name:     "密码格式不正确",reqBody:  `{"email": "123@qq.com", "pwd": "invalidpassword"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "密码必须大于8位,包含数字、特殊字符"}`,},{name:     "邮箱或密码不匹配",reqBody:  `{"email": "123123@qq.com", "pwd": "hello#world123"}`,wantCode: http.StatusBadRequest,wantBody: `{"msg": "邮箱或密码不匹配!"}`,},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {// 创建一个 gin 的上下文server := gin.Default()h := NewUserHandler()h.RegisterRoutes(server)// mock 创建一个 http 请求req, err := http.NewRequest(http.MethodPost,                     // 请求方法"/user/login",                       // 请求路径bytes.NewBuffer([]byte(tc.reqBody)), // 请求体)// 断言没有错误assert.NoError(t, err)// 设置请求头req.Header.Set("Content-Type", "application/json")// 创建一个响应resp := httptest.NewRecorder()// 服务端处理请求server.ServeHTTP(resp, req)// 断言响应码和响应体assert.Equal(t, tc.wantCode, resp.Code)// 断言 JSON 字符串是否相等assert.JSONEq(t, tc.wantBody, resp.Body.String())})}
}

在这个例子中,我们创建了一个简单的 HTTP 请求,TestUserHandler_Login 函数定义了一个测试函数,用于测试用户登录功能的不同情况。

  1. testCases 列表定义了多个测试用例,每个测试用例包含了测试名称、请求体、期望的 HTTP 状态码和期望的响应体内容。
  2. 使用 for 循环遍历测试用例列表,每次循环创建一个新的测试子函数,并在其中模拟 HTTP 请求发送给登录接口。
  3. 在每个测试子函数中,先创建一个 Gin 的默认上下文和用户处理器 UserHandler,然后注册路由并创建一个模拟的 HTTP 请求。
  4. 通过 httptest.NewRecorder() 创建一个响应记录器,使用 server.ServeHTTP(resp, req) 处理模拟请求,得到响应结果。
  5. 最后使用断言来验证实际响应的 HTTP 状态码和响应体是否与测试用例中的期望一致。

最后,使用Goland 运行测试,结果如下:

二、gock

2.1介绍

gock 可以帮助你在测试过程中模拟 HTTP 请求和响应,这对于测试涉及外部 API 调用的应用程序非常有用。它可以让你轻松地定义模拟请求,并验证你的应用程序是否正确处理了这些请求。

GitHub 地址:github.com/h2non/gock

2.2 安装

你可以通过以下方式安装 gock:

go get -u github.com/h2non/gock

导入 gock 包:

import "github.com/h2non/gock"

2.3 基本使用

gock 的基本用法如下:

  1. 启动拦截器:在测试开始前,使用 gock.New 函数启动拦截器,并指定你想要拦截的域名和端口。
  2. 定义拦截规则:你可以使用 gock.Intercept 方法来定义拦截规则,比如拦截特定的 URL、方法、头部信息等。
  3. 设置响应:你可以使用 gock.NewJsongock.NewText 等方法来设置拦截后的响应内容。
  4. 运行测试:在定义了拦截规则和响应后,你可以运行测试,gock 会拦截你的 HTTP 请求,并返回你设置的响应。

2.4 举个例子

2.4.1 前置代码

如果我们是在代码中请求外部API的场景(比如通过API调用其他服务获取返回值)又该怎么编写单元测试呢?

例如,我们有以下业务逻辑代码,依赖外部API:http://your-api.com/post提供的数据。

// ReqParam API请求参数
type ReqParam struct {X int `json:"x"`
}// Result API返回结果
type Result struct {Value int `json:"value"`
}func GetResultByAPI(x, y int) int {p := &ReqParam{X: x}b, _ := json.Marshal(p)// 调用其他服务的APIresp, err := http.Post("http://your-api.com/post","application/json",bytes.NewBuffer(b),)if err != nil {return -1}body, _ := ioutil.ReadAll(resp.Body)var ret Resultif err := json.Unmarshal(body, &ret); err != nil {return -1}// 这里是对API返回的数据做一些逻辑处理return ret.Value + y
}

在对类似上述这类业务代码编写单元测试的时候,如果不想在测试过程中真正去发送请求或者依赖的外部接口还没有开发完成时,我们可以在单元测试中对依赖的API进行mock。

2.4.2 测试用例

使用gock对外部API进行mock,即mock指定参数返回约定好的响应内容。 下面的代码中mock了两组数据,组成了两个测试用例。

package gock_demoimport ("testing""github.com/stretchr/testify/assert""gopkg.in/h2non/gock.v1"
)func TestGetResultByAPI(t *testing.T) {defer gock.Off() // 测试执行后刷新挂起的mock// mock 请求外部api时传参x=1返回100gock.New("http://your-api.com").Post("/post").MatchType("json").JSON(map[string]int{"x": 1}).Reply(200).JSON(map[string]int{"value": 100})// 调用我们的业务函数res := GetResultByAPI(1, 1)// 校验返回结果是否符合预期assert.Equal(t, res, 101)// mock 请求外部api时传参x=2返回200gock.New("http://your-api.com").Post("/post").MatchType("json").JSON(map[string]int{"x": 2}).Reply(200).JSON(map[string]int{"value": 200})// 调用我们的业务函数res = GetResultByAPI(2, 2)// 校验返回结果是否符合预期assert.Equal(t, res, 202)assert.True(t, gock.IsDone()) // 断言mock被触发
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/312413.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网红泡泡机单片机方案开发定制

酷得(i-coder)是一家专业的技术服务公司,致力于为各类智能硬件提供高效、稳定、安全的底层驱动解决方案。我们拥有一支经验丰富、技术精湛的团队,能够为客户提供全方位的底层驱动开发服务。 下面是酷得的开发流程: 1…

记录一次k8s pod之间ip无法访问,问题排查与定位

记录一次k8s pod之间ip无法访问,问题排查与定位 问题展现现象 node之间通信正常 部分node上的pod无法通信 排查有问题node 使用启动网络测试工具 环境准备 docker 数据库mysql 使用有状态副本集合 --- apiVersion: apps/v1 kind: StatefulSet metadata:anno…

Win10本地更新无法升级win11 的0x80080005解决方法

Win10本地更新无法升级win11 Visual Studio 2022 运行项目时,本文提供了错误“指定的程序需要较新版本的 Windows”的解决方法。 更新时提示:0x80080005 解决方法 1、下载Windows11InstallationAssistant.exe 【免费】Windows11InstallationAssista…

基于GAN的图像补全实战

数据与代码地址见文末 论文地址:http://iizuka.cs.tsukuba.ac.jp/projects/completion/data/completion_sig2017.pdf 1.概述 图像补全,即补全图像中的覆盖和缺失部分, 网络整体结构如下图所示,整体网络结构还是采取GAN,对于生成器,网络结构采取Unet的形式,首先使用卷积…

高级数据结构与算法(8)

一、选择题 1、Rod-cutting Problem: Given a rod of total length N inches and a table of selling prices PL​ for lengths L=1,2,⋯,M. You are asked to find the maximum revenue RN​ obtainable by cutting up the rod and selling the pieces. For example, based o…

SpringBoot和Axios数据的传递和接收-Restful完全版

文章目录 一、基础知识铺垫Axios使用HTTP请求方式数据传输方式SpringBoot获取数据的方式 二、基础传递代码示例(一)Path Variables(二)Get、DeleteRequestParamModelAttribute (三)Post、Put、PatchRequest…

2024年五一杯数学建模B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间:2024…

DataX案例,MongoDB数据导入HDFS与MySQL

【尚硅谷】Alibaba开源数据同步工具DataX技术教程_哔哩哔哩_bilibili 目录 1、MongoDB 1.1、MongoDB介绍 1.2、MongoDB基本概念解析 1.3、MongoDB中的数据存储结构 1.4、MongoDB启动服务 1.5、MongoDB小案例 2、DataX导入导出案例 2.1、读取MongoDB的数据导入到HDFS 2…

期货开户只要跟随于市场即可

仓,和时间无关;和价格运动的距离长短也很少关联,如果有的话,就是看价格运动的边界是否已经到达或准备穿越,价格运动的边界,其实很好辨认(说的好,精辟)。十五分钟以上图形…

分类预测 | Matlab实现RIME-LSSVM霜冰算法优化最小二乘支持向量机数据分类预测

分类预测 | Matlab实现RIME-LSSVM霜冰算法优化最小二乘支持向量机数据分类预测 目录 分类预测 | Matlab实现RIME-LSSVM霜冰算法优化最小二乘支持向量机数据分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现RIME-LSSVM霜冰算法优化最小二乘支持向量机数…

机器学习和深度学习--李宏毅(笔记与个人理解)Day9

Day9 Logistic Regression(内涵,熵和交叉熵的详解) 中间打了一天的gta5,图书馆闭馆正好npy 不舒服那天天气不好,哈哈哈哈哈总之各种理由吧,导致昨天没弄起来,今天补更! 这里重点注意…

【植物大战僵尸融合机器学习】+源码

上期回顾: 今天给大家推荐一个Gtihub开源项目:PythonPlantsVsZombies,翻译成中就是植物大战僵尸。 《植物大战僵尸》是一款极富策略性的小游戏。可怕的僵尸即将入侵,每种僵尸都有不同的特点,例如铁桶僵尸拥有极强的抗…

【Android AMS】startActivity流程分析

文章目录 AMSActivityStackstartActivity流程startActivityMayWaitstartActivityUncheckedLocked startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition)resumeTopActivityLocked 参考 AMS是个用于管理Activity和其它组件运行…

网络靶场实战-PE 自注入

默认的 Windows API 函数(LoadLibrary、LoadLibraryEx)只能加载文件系统中的外部库,无法直接从内存中加载 DLL,并且无法正确地加载 EXE。有时候,确实需要这种功能(例如,不想分发大量文件或者想增…

API管理平台:你用的到底是哪个?

Apifox是不开源的,在github的项目只是readme文件,私有化需要付费。当然saas版目前是免费使用的。 一、Swagger 为了让Swagger界面更加美观,有一些项目可以帮助你实现这一目标。以下是一些流行的项目,它们提供了增强的UI和额外的功…

Axure中继器排序失效 /没变化解决

问题复现 通过设置交互条件后,但是没效果,查了很多资料,按照教程操作,仍旧没效果。 原因 结论先行:问题出在汉化包,你用了汉化包导致axure内部出错。最简单的办法,删除汉化文件,…

AI应用实战2:使用scikit-learn进行回归任务实战

代码仓库在gitlab,本博客对应于02文件夹。 1.问题分析 在此篇博客中我们来对回归任务进行实战演练,背景是直播带货平台的业绩预测。第一步,就是分析问题。 问题痛点: 在直播带货平台上,由于市场环境多变、用户行为复…

SSH协议的优缺点

SSH(Secure Shell)是一种用于在计算机网络上进行安全远程访问和执行命令的协议。提供加密通信通道,防止敏感信息在传输过程中被窃听或篡改。SSH还支持文件传输和端口转发等功能,使其成为广泛使用的安全远程管理工具。 1. 安全远程…

SQLite的PRAGMA 声明(二十三)

返回:SQLite—系列文章目录 上一篇:SQLite从出生到现在(发布历史记录)(二十二) 下一篇:用于 SQLite 的异步 I/O 模块(二十四) PRAGMA 语句是特定于 SQLite 的 SQL 扩…

Linux知识点(3)

文章目录 11. 进程间通信11.1 管道11.1.0 |11.1.1 匿名管道11.1.2 命名管道11.1.3 用匿名管道形成进程池 11.2 system V共享内存11.2.1 system V函数11.2.2 system 命令 11.3 system V消息队列11.4 system V 信号量 12. 进程信号12.1 前台进程和后台进程12.1.1 jobs12.1.2 fg &…