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.mzph.cn/news/822967.shtml

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

相关文章

eNSP 静态路由综合实验

eNSP静态路由综合实验涉及的知识点包括但不限于:IP地址配置、环回地址配置、合理进行子网汇总、缺省路由、空路由接口、浮动静态路由、ACL表规则撰写、动态NAT网络地址转换、端口映射、远程登陆等。在实验过程中,可能需要根据给定的拓扑结构和要求&#…

Flume配置案例@Source:端口,Channel:内存,Sink:控制台

vim /opt/module/flume/job/port_to_console.conf ------------------------- 在nc-flume-log.conf文件中添加如下内容。 # Name the components on this agent a1.sources r1 a1.sinks k1 a1.channels c1# Describe/configure the source a1.sources.r1.type netcat a1.so…

XGB-26:model

切片树模型|Slice tree model 当XGBoost中的 booster 参数设置为 gbtree 或 dart 时,算法构建了一个由多棵树组成的树模型。这个树模型可以被切片成多个子模型,每个子模型包含原始模型中一部分树。这个切片过程允许创建更小、更专业的模型,专…

每日OJ题_01背包④_力扣1049. 最后一块石头的重量 II

目录 力扣1049. 最后一块石头的重量 II 问题解析 解析代码 滚动数组优化代码 力扣1049. 最后一块石头的重量 II 1049. 最后一块石头的重量 II 难度 中等 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从…

数字乳腺癌组织病理学图像分类的Vision Transformer及其变体

Vision Transformer作为一种基于自注意力机制的高效图像分类工具被提出。近年来出现了基于Poolingbased Vision Transformer (PiT)、卷积视觉变压器(CvT)、CrossFormer、CrossViT、NesT、MaxViT和分离式视觉变压器(SepViT)等新模型。 它们被用于BreakHis和IDC数据集上的图像分…

软件项目管理 - PERT 图

文章目录 1 概述1.1 PERT 图1.2 基础概念 2 相关计算2.1 最早时刻2.2 最迟时刻2.3 关键路径2.4 松弛时间 1 概述 1.1 PERT 图 PERT:Program Evaluation and Review Technique(项目评估与评审技术) PERT 图是一个有向图,图中的箭…

【C++造神计划】数学运算

​ 数学运算库 //二者选一 #include <cmath> #include <math.h>// #include <math.h> #include <cmath> #include <stdio.h>int main() {float res;res sqrt(2);res abs(-5.3);res sin(0.5*M_PI);res asin(res);res cos(0.5*M_PI);res a…

通过Dockerfile 创建 kali-novnc

创建Dockerfile # 使用官方Kali镜像作为基础镜像 FROM kalilinux/kali-rolling# 设置工作目录 WORKDIR /app# 将当前目录下的所有文件复制到工作目录中 COPY ./run.sh .# 安装项目依赖 RUN apt update -y RUN apt upgrade -y RUN apt install dbus-x11 xfce4 tightvncserver …

【c++】stack和queue使用 stack和queue模拟实现

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1. stack的介绍和使用 1.1 stack的介绍 1.2 stack的使用 1.3 stack的模拟实现 2. queue的介绍和使用 2.1 queue的介绍 2.2 queue的…

Unity中使用NewtonJson序列化继承类时报错解决方法参考

在Unity中使用NewtonJson时&#xff0c;如果要序列化的类继承了别的类&#xff0c;可能报如下错误&#xff1a; JsonSerializationException: Self referencing loop detected for property...... 解决的方法是新建一个JsonSerializerSettings对象&#xff0c;并设置对象的Ref…

Golang面试题五(GC)

目录 1.Golang GC版本 2.常见的垃圾回收算法有以下几种 3.怎么找到程序中无用的对象 引用计数法 根搜索法 GC roots对象 4.java与go的GC对比 5.三色标记法 1.Golang GC版本 Go 1.3版本&#xff1a;普通标记清除法&#xff0c;整体过程需要启动STW&#xff0c;效率极低。…

SpringBoot之JWT令牌校验

SpringBoot之JWT令牌校验 本文根据黑马b站springboot3vue3课程 JWT &#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在不同实体之间安全地传输信息。它由三个部分组成&#xff1a;头部&#xff08;Header&#xff09;…

如何实现音乐音频合并?分享3种简单的合并技巧!音频合并的方法

音乐合并&#xff0c;作为一种音乐创作与编辑的手法&#xff0c;已经逐渐在音乐制作领域占据了一席之地。音乐合并不仅是对音乐元素的重新组合&#xff0c;更是对音乐内涵的深化和拓展。它可以将不同的音乐风格和元素巧妙地融合在一起&#xff0c;创造出全新的听觉体验。 一&a…

DonkeyDocker-v1-0渗透思路

MY_BLOG https://xyaxxya.github.io/2024/04/13/DonkeyDocker-v1-0%E6%B8%97%E9%80%8F%E6%80%9D%E8%B7%AF/ date: 2024-04-13 19:15:10 tags: 内网渗透Dockerfile categories: 内网渗透vulnhub 靶机下载地址 https://www.vulnhub.com/entry/donkeydocker-1,189/ 靶机IP&a…

芯片设计围炉札记

文章目录 语言Verilog 和 VHDL 区别 芯片验证 语言 System Verilog的概念以及与verilog的对比 IC 设计软件分析 Verilog 和 VHDL 区别 Verilog HDL 和 VHDL 的区别如下&#xff1a; 语法结构&#xff1a;Verilog的语法结构类似于C语言&#xff0c;而VHDL的语法结构则更接近…

苍穹外卖学习记录(一)

1.JWT令牌认证 JSON Web Token (JWT)是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任&#xff0c;因为它是数字签名的。 JWT是目前最常用的一种令牌规范&#xff0c;它最…

QT-编译报库错误(LF/CRLF)

QT-安装后环境问题记录 版本和环境问题 版本和环境 QT5.15.2 Windows10 QT Creator 问题 在QT夸端开发的项目中 &#xff0c;使用QTCreator打开项目pro文件&#xff0c;编译报出很多系统库 及本地文件中的一些问题&#xff0c;具体如图&#xff1a; 后续&#xff0c;我以为…

L1-059 敲笨钟

原题链接&#xff1a;https://pintia.cn/problem-sets/994805046380707840/exam/problems/1111914599412858880?type7&page0 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 微博上有个自称“大笨钟V”的家伙&#xff0c;每天敲钟催促码农们爱惜身体早点睡觉…

数字人结合动捕设备化身虚拟主持人如何玩转大型活动?

由十五运会和残特奥会广州赛区执委会、广州市政府新闻办、广州市科学技术局联合举办的“科技赋能 畅想全运”故事会上&#xff0c;数字人“小运”结合动捕设备化身虚拟主持人惊喜亮相&#xff0c;与真人主持人趣味互动&#xff0c;并向大众介绍了其后续将在大运会上给运动员、工…

photoshop2022增效工具ICOFormat.8bi(PS ico插件)

先退出关闭ps 1、下载插件压缩包&#xff0c;解压出ICOFormat.8bi文件&#xff0c;有两个版本ICOFormat64.8bi对应32位版、ICOFormat64.8bi对应64位版本。 2、把解压后的ICOFormat64.8bi文件覆盖到Photoshop安装目录: C:\Program Files\Adobe\Adobe Photoshop 2022\Required…