ec9d25fef6
- 在 Api 和 ApiCase 中新增 AnyMsg 方法,允许在状态不为 0 时跳过错误消息的断言,适用于动态内容的用例。 - 更新文档说明,介绍 AnyMsg 的用法及其链式调用示例。 - 在示例测试中添加 AnyMsg 的使用案例,展示其在处理动态错误消息时的便利性。
241 lines
8.7 KiB
Go
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(见覆盖率报告「未通过的接口」)
|
|
}},
|
|
}
|