IRIS单元测试需用httptest.New启动内存服务器,newApp()须返回未Run的*iris.Application实例;路由、参数、中间件须严格匹配;并发测试需隔离全局状态;应覆盖空列表、参数异常等边界分支。

用 httptest.New 搭建测试客户端实例
IRIS 的单元测试不依赖外部 HTTP 服务,核心是 httptest.New —— 它会启动一个内存级测试服务器,绕过网络栈,直接走路由分发逻辑。你传入的 newApp() 函数必须返回一个完整的 *iris.Application 实例,且不能在其中调用 app.Run(),否则测试会卡死或 panic。
常见错误现象:panic: listen tcp :8080: bind: address already in use,说明测试里误写了 app.Run();或者 e.GET("/").Expect().Status(200) 报空指针,大概率是 newApp() 返回了 nil 或没正确注册路由。
-
httptest.New(t, newApp())中的t必须是当前测试函数的*testing.T,不可复用其他测试的t - 每个测试函数应独立构造
e,避免跨测试共享状态(比如共用同一个userList全局变量) - 若路由依赖中间件(如 JWT 验证),需在
newApp()中显式挂载,否则测试时会被跳过
GET/POST 请求断言要匹配实际路由和参数方式
IRIS 路由定义和请求发送方式必须严格对应。比如你的控制器方法是 PostImport(),它默认绑定到 POST /import,但表单参数名是 users(来自 c.Ctx.FormValue("users")),不是 user 或 data。
容易踩的坑:e.POST("/import").WithFormField("user", "a,b").Expect().Status(200) 会失败,因为后端取的是 "users";又或者用 WithJSON 发送 JSON,但 handler 没调用 c.Ctx.ReadJSON(),导致参数为空。
- 表单提交用
WithFormField("users", "u1,u2") - JSON 提交先确保 handler 有
var req struct{...}; c.Ctx.ReadJSON(&req),再用WithJSON(map[string]string{"users": "u1,u2"}) -
GET /lucky这类无参接口,直接e.GET("/lucky").Expect()即可,无需额外设置
并发测试必须隔离全局状态
你示例中的 userList 是包级变量,在并发测试中会互相污染。比如 TestA 导入 3 个用户,TestB 同时执行抽奖,可能抽到 TestA 的用户,也可能因长度突变 panic。
根本解法不是加锁,而是让每次测试拥有独立数据上下文。IRIS 本身不提供 request-scoped 变量,所以得靠测试层模拟:把 userList 改成 controller 字段,并在 newApp() 中为每个测试实例注入新切片。
- 不要在测试函数里直接操作全局
userList = []string{},这无法保证执行顺序 - 改用结构体字段:
type lotteryController struct { Ctx iris.Context; Users []string },并在Handle(&lotteryController{Users: make([]string, 0)})中传入新实例 - 如果必须复用原逻辑,可在每个测试开头用
defer func() { userList = []string{} }()清理,但仅限单协程测试
覆盖率低?重点补边界分支和错误路径
IRIS 接口的单元测试常忽略 404、空列表、参数缺失等分支。比如 GetLucky() 有 count == 0、count == 1、count > 1 三条路径,但多数人只测了最后一种。
真实项目里,这些分支恰恰最容易出线上问题:前端忘了调 /import 就点抽奖,返回的提示文案是否清晰?有没有 panic?
- 对
GetLucky()写三个测试:空列表、单用户、多用户,分别断言响应体是否含指定字符串 - 对
PostImport()测试空字符串、重复用户名、含空格用户名,验证去重和 trim 行为 - 用
.Expect().Body().Equal(...)比.Status(200)更可靠,HTTP 状态码掩盖了业务逻辑错误


















