Revel框架单元测试必须通过TestSuite构造完整上下文实例,手动设置路由参数、Session和请求体,调用result.Apply(controller)后断言输出,且每个测试需新建TestSuite避免状态污染。

Revel 框架的单元测试不是调用函数那么简单,它必须走真实请求链路或模拟完整上下文,否则 nil pointer dereference 和空响应是常态。
TestSuite 必须匿名嵌入指针类型
错例:type AppTest struct { ts revel.TestSuite } —— 字段名非匿名,所有 t.Get、t.AssertOk 都会报未定义。对例只有这一种写法:type AppTest struct { *revel.TestSuite }。如果用了别名(如 rev),必须全程统一,不能混用 revel.TestSuite 和 rev.TestSuite。
t.Get 会卡住?检查 testrunner 是否启用
t.Get 不是普通 HTTP 请求,它依赖 Revel 的 testrunner 模块启动 mini app 实例。没启用就会阻塞在 http.DefaultClient.Do。确认两点:
-
app.conf中有module.testrunner = github.com/robfig/revel/modules/testrunner -
conf/routes中有GET /testrunner TestRunner.Index - 运行命令必须是
revel test [app-name] [env],不是go test
Before() 里不能做断言或发请求
Before() 是初始化钩子,不是测试执行阶段。此时 t.Response 和 t.ResponseBody 还没分配,调用 t.Get("/") 或 t.AssertOk() 会直接 panic。正确用途只包括:
- 替换
t.Client(例如用带 timeout 的自定义 client) - 重置全局状态(如清空 mock DB)
- 设置测试专用配置(如关闭日志输出)
After() 同理,只做清理,不读响应、不断言。
ResponseBody 是 []byte,别直接用 string 包含判断
t.ResponseBody 是原始字节切片,不是字符串。写 strings.Contains(string(t.ResponseBody), "hello") 看似可行,但容易因编码或空格差异漏判。更稳妥的是:
- 用
bytes.Contains(t.ResponseBody, []byte("hello")) - 对 JSON 响应,先
json.Unmarshal再结构比对,避免字段顺序、换行等干扰 - 若需正则匹配,先转
string(t.ResponseBody)再操作,但注意大响应体的内存开销
最常被忽略的一点:t.Get 后必须立刻断言或读取 t.ResponseBody,因为下一次 t.Get 会覆盖它 —— 没有自动缓存,也没有历史记录。


















