Files
hotime/example/app/expect_demo_test.go
T
hoteas ec9d25fef6 feat(testing): 新增 AnyMsg 方法以跳过错误消息断言
- 在 Api 和 ApiCase 中新增 AnyMsg 方法,允许在状态不为 0 时跳过错误消息的断言,适用于动态内容的用例。
- 更新文档说明,介绍 AnyMsg 的用法及其链式调用示例。
- 在示例测试中添加 AnyMsg 的使用案例,展示其在处理动态错误消息时的便利性。
2026-07-22 04:38:38 +08:00

241 lines
8.7 KiB
Go

package app
import (
"fmt"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
var ExpectDemoTest = CtrTest{
// ─── 基础类型结构校验 + Verify 值断言 ──────────────────
"string_result": {Desc: "返回字符串-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetString("result")
if result != "操作成功" {
return fmt.Errorf("result 期望 '操作成功', 实际 '%s'", result)
}
return nil
}).Get("result是字符串", 0, "样本")
}},
"number_result": {Desc: "返回整数-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetCeilInt64("result")
if result != 42 {
return fmt.Errorf("result 期望 42, 实际 %d", result)
}
return nil
}).Get("result是整数", 0, int64(1))
}},
"float_result": {Desc: "返回小数-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetFloat64("result")
if result != 3.14 {
return fmt.Errorf("result 期望 3.14, 实际 %f", result)
}
return nil
}).Get("result是小数", 0, float64(1.0))
}},
"bool_result": {Desc: "返回布尔-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetBool("result")
if !result {
return fmt.Errorf("result 期望 true, 实际 false")
}
return nil
}).Get("result是布尔", 0, true)
}},
"map_result": {Desc: "返回对象-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetMap("result")
if result == nil {
return fmt.Errorf("result 为 nil")
}
if result.GetCeilInt64("id") != 1 {
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
}
if result.GetString("name") != "示例商品" {
return fmt.Errorf("name 期望 '示例商品', 实际 '%s'", result.GetString("name"))
}
if !result.GetBool("in_stock") {
return fmt.Errorf("in_stock 期望 true")
}
return nil
}).Get("result是Map-校验字段名+类型+值", 0, Map{
"id": int64(1), "name": "sample", "price": float64(1.0), "in_stock": true,
})
}},
"nested_result": {Desc: "返回嵌套对象-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetMap("result")
if result == nil {
return fmt.Errorf("result 为 nil")
}
if result.GetCeilInt64("id") != 100 {
return fmt.Errorf("id 期望 100, 实际 %d", result.GetCeilInt64("id"))
}
if result.GetString("title") != "测试订单" {
return fmt.Errorf("title 期望 '测试订单', 实际 '%s'", result.GetString("title"))
}
customer := result.GetMap("customer")
if customer == nil {
return fmt.Errorf("customer 为 nil")
}
if customer.GetString("name") != "张三" {
return fmt.Errorf("customer.name 期望 '张三', 实际 '%s'", customer.GetString("name"))
}
if customer.GetString("phone") != "13800138000" {
return fmt.Errorf("customer.phone 期望 '13800138000', 实际 '%s'", customer.GetString("phone"))
}
items := result.GetSlice("items")
if len(items) != 2 {
return fmt.Errorf("items 期望 2 条, 实际 %d 条", len(items))
}
firstItem := items.GetMap(0)
if firstItem.GetString("goods_name") != "苹果" {
return fmt.Errorf("items[0].goods_name 期望 '苹果', 实际 '%s'", firstItem.GetString("goods_name"))
}
delivery := result.GetMap("extra").GetMap("delivery")
if delivery == nil {
return fmt.Errorf("extra.delivery 为 nil")
}
if delivery.GetString("address") != "XX路1号" {
return fmt.Errorf("extra.delivery.address 期望 'XX路1号', 实际 '%s'", delivery.GetString("address"))
}
return nil
}).Get("深层嵌套Map+Slice+Map", 0, Map{
"id": int64(1), "title": "sample",
"customer": Map{"id": int64(1), "name": "sample", "phone": "sample"},
"items": Slice{Map{
"id": int64(1), "goods_name": "sample", "quantity": int64(1), "price": float64(1.0),
}},
"extra": Map{
"delivery": Map{"address": "sample", "fee": int64(1)},
},
})
}},
"slice_result": {Desc: "返回数组-结构+值校验", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetSlice("result")
if len(result) != 2 {
return fmt.Errorf("result 期望 2 条, 实际 %d 条", len(result))
}
first := result.GetMap(0)
if first.GetString("name") != "标签A" {
return fmt.Errorf("result[0].name 期望 '标签A', 实际 '%s'", first.GetString("name"))
}
second := result.GetMap(1)
if second.GetString("name") != "标签B" {
return fmt.Errorf("result[1].name 期望 '标签B', 实际 '%s'", second.GetString("name"))
}
return nil
}).Get("result直接是Slice", 0, Slice{Map{"id": int64(1), "name": "sample"}})
}},
// ─── 错误先行 + 正确请求完整范式 ─────────────────────
"error_demo": {Desc: "错误先行+正确请求完整校验", Func: func(a *Api) {
// ======== 第一步:错误用例(先行) ========
a.Form(Map{"name": ""}).Post("名称为空-缺少必填字段", 3, "名称不能为空")
// AnyMsg:跳过错误 msg 断言(仅断言 status)。用于响应 msg 含网络错误等
// 动态内容的用例——desc 与实际 msg 不一致时,无 AnyMsg 会因 desc 兜底匹配而失败
a.Form(Map{"name": ""}).AnyMsg().Post("AnyMsg跳过msg断言-desc与实际msg不一致", 3)
// ======== 第二步:正确请求 + 结构校验 + Verify 值断言 ========
a.Form(Map{"name": "测试商品"}).
Verify(func(a *Api) error {
result := a.Resp().GetBody().GetMap("result")
if result == nil {
return fmt.Errorf("result 为 nil")
}
if result.GetCeilInt64("id") != 1 {
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
}
if result.GetString("name") != "测试商品" {
return fmt.Errorf("name 期望 '测试商品', 实际 '%s'", result.GetString("name"))
}
if !result.GetBool("created") {
return fmt.Errorf("created 期望 true")
}
return nil
}).
Post("名称正确-结构+值校验", 0, Map{
"id": int64(1), "name": "sample", "created": true,
})
}},
"no_expect": {Desc: "最小正确用例-仍需值断言", Func: func(a *Api) {
a.Verify(func(a *Api) error {
result := a.Resp().GetBody().GetMap("result")
if result == nil {
return fmt.Errorf("result 为 nil")
}
if result.GetCeilInt64("id") != 1 {
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
}
if result.GetString("data") != "无预期校验" {
return fmt.Errorf("data 期望 '无预期校验', 实际 '%s'", result.GetString("data"))
}
return nil
}).Get("有Verify值断言", 0, Map{"id": int64(1), "data": "sample"})
}},
// ─── Verify 完整范式:错误 → 数据准备 → 正确请求 + 响应断言 + DB 校验 ─
"verify_demo": {Desc: "Verify完整范式演示", Func: func(a *Api) {
// ======== 第一步:错误用例 ========
a.Form(Map{"name": ""}).Post("名称为空", 3, "名称不能为空")
// ======== 第二步:准备测试数据 ========
// 先插入一条已有记录,用于后续 DB 校验时确认"新增"行为
a.DB().Insert("test_batch", Map{
"name": "existing_record", "title": "verify_demo", "state": 1,
})
// ======== 第三步:正确请求 + Verify 统一校验 ========
name := "verify_test"
a.Form(Map{"name": name}).
Verify(func(a *Api) error {
// 响应值断言
result := a.Resp().GetBody().GetMap("result")
if result == nil {
return fmt.Errorf("result 为 nil")
}
if result.GetCeilInt64("id") <= 0 {
return fmt.Errorf("返回的 id 必须大于 0, 实际 %d", result.GetCeilInt64("id"))
}
if result.GetString("name") != name {
return fmt.Errorf("name 期望 '%s', 实际 '%s'", name, result.GetString("name"))
}
// 数据库状态校验
row := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": name, "title": "verify_demo"}})
if row == nil {
return fmt.Errorf("test_batch 表未找到 name=%s 的记录", name)
}
if row.GetCeilInt64("state") != 0 {
return fmt.Errorf("test_batch.state 期望 0, 实际 %d", row.GetCeilInt64("state"))
}
// 校验之前插入的记录仍然存在(验证不会误删)
existing := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": "existing_record", "title": "verify_demo"}})
if existing == nil {
return fmt.Errorf("之前插入的 existing_record 丢失")
}
return nil
}).
Post("写入并校验DB+响应", 0, Map{"id": int64(1), "name": "sample"})
// Verify 返回 error 会使该用例失败并写入 failReason(见覆盖率报告「未通过的接口」)
}},
}