ApiCall
以宣告式語法執行 HTTP API 請求
ApiCall 讓你直接在 Gherkin 中宣告 API 呼叫的參數與回應擷取,無需撰寫 HTTP 請求的程式碼。支援 DataTable 和 JSON 兩種格式。
兩種格式
| 格式 | 適用場景 |
|---|---|
| DataTable | 扁平欄位,直覺易讀 |
| DocString JSON | 巢狀結構,貼近實際 Request Body |
DataTable 格式
When (UID="$Alice.id") 新增待辦事項, call table:
| >todo1.id | title |
| <todoId | 買牛奶 |JSON 格式
When (UID="$Alice.id") 新增待辦事項, call JSON:
"""json
{
>todo1.id: <todoId,
"title": "買牛奶"
}
"""兩者效果相同:
- 以
$Alice.id作為呼叫者身份(Bearer Token) - 呼叫 API Spec 中 summary 為「新增待辦事項」的 API
- 將
title: 買牛奶作為 Request Body - 從回應中擷取
todoId欄位,存入變數todo1.id
JSON 格式注意事項:符號系統的表達式(
$變數、&CAS 約束、@時間符號、>/<擷取鍵)不可放在 JSON 字串引號""內,否則會被視為純字串。{ "title": $todo1.title } // 正確:符號在引號外 { "title": "$todo1.title" } // 錯誤:符號被引號包裹,視為純字串
欄位語法
DataTable 的欄位名稱支援特殊前綴,用於指定參數類型:
| 前綴 | 用途 | 範例 |
|---|---|---|
| (無) | Request Body 欄位;若欄位名稱同時存在於 API Spec 的 Path/Query/Header 參數中,會自動帶入對應位置(等同同時填寫 P:/Q:/H:) | title、todoId |
P: | Path 參數 | P:todoId → 替換 URL 中的 {todoId} |
Q: | Query 參數 | Q:page → ?page=value |
H: | Request Header | H:X-Trace-Id |
B: | 顯式標記整個 Request Body(裸值、陣列、物件) | B: → 將 cell 值作為整個 body |
> | 存入變數 — 定義要儲存回應值的變數名稱 | >todo1.id |
< | 取自回應 — 指定回應 JSON 中的來源欄位 | <todoId |
<H: | 取自回應 Header — 指定回應 Header 的來源欄位 | <H:Location |
<B: | 取自回應 Body(裸值) — 擷取整個 response body | <B: |
JSON 格式的前綴語法
JSON 格式使用大寫字母前綴加引號的語法,對應 DataTable 的 P:/Q:/H: 前綴:
| DataTable | JSON | 說明 |
|---|---|---|
P:todoId | P"todoId" | Path 參數 |
Q:page | Q"page" | Query 參數 |
H:X-Trace-Id | H"X-Trace-Id" | Request Header |
B: | B: | 顯式標記整個 Body |
# DataTable 版本
When (UID="$Alice.id") 更新待辦事項, call table:
| P:todoId | Q:verbose | H:X-Trace | title |
| $todo1.id | true | trace-123 | 買豆漿 |
# JSON 版本
When (UID="$Alice.id") 更新待辦事項, call JSON:
"""json
{
P"todoId": $todo1.id,
Q"verbose": "true",
H"X-Trace": "trace-123",
"title": "買豆漿"
}
"""擷取變數配對
> 和 < 必須在同一欄中上下配對:header 放變數名(>),row 放來源欄位(<)。
| >todo1.id | >todo1.title | title |
| <todoId | <title | 買牛奶 |執行後:
todo1.id= 回應中的todoId值todo1.title= 回應中的title值
擷取 Response Header
使用 <H: 前綴擷取回應中的 HTTP Header 值:
# DataTable — 擷取 Location header
When (No Actor) 新增資源, call table:
| >loc | name |
| <H:Location | 測試資源 |
# JSON — 擷取 X-Request-Id header
When (No Actor) 新增資源, call JSON:
"""json
{
>requestId: <H"X-Request-Id",
"name": "測試資源"
}
"""擷取裸值 Response Body
當 API 回傳裸值(如 true、42)而非 JSON 物件時,使用 <B: 擷取整個 response body:
When (No Actor) 查詢資源狀態, call table:
| >result | P:id |
| <B: | $res1 |與 API Spec 的對應關係
ApiCall 指令透過 summary 欄位識別要呼叫的 API。這個設計讓測試檔案使用業務語言,而非技術性的 URL 路徑。
對應機制
# api-spec.yml (OpenAPI 3.0)
paths:
/todos:
post:
summary: 新增待辦事項 # ← ApiCall 透過這個 summary 識別
operationId: createTodo
requestBody:
content:
application/json:
schema:
type: object
required:
- title
properties:
title:
type: string
priority:
type: integer
responses:
"201":
content:
application/json:
schema:
type: object
properties:
todoId:
type: integer
title:
type: string# 對應的 Gherkin 語法
When (UID="$Alice.id") 新增待辦事項, call table:
| title | priority |
| 買牛奶 | 1 |對應表
| Gherkin 元素 | API Spec 來源 |
|---|---|
新增待辦事項 | paths.*.*.summary |
| HTTP Method | 從 path 定義推斷(post, get, put, delete) |
| URL Path | paths 的 key(如 /todos) |
| Request Body 欄位 | requestBody.content.*.schema.properties |
| Path 參數 | parameters[].in: path |
| Query 參數 | parameters[].in: query |
| Response 欄位 | responses.*.content.*.schema.properties |
summary 唯一性要求
每個 API 操作的 summary 在整份 API Spec 中必須唯一:
# ✅ 正確:summary 各不相同
/todos:
post:
summary: 新增待辦事項
get:
summary: 查詢待辦事項列表
/todos/{id}:
get:
summary: 查詢單一待辦事項
put:
summary: 更新待辦事項
delete:
summary: 刪除待辦事項
# ❌ 錯誤:重複的 summary 會導致無法識別
/todos:
get:
summary: 查詢待辦事項
/todos/{id}:
get:
summary: 查詢待辦事項 # 重複!自動參數分類
當 Body 欄位名稱與 API Spec 中的 Path/Query/Header 參數同名時,該值會同時作為 Body 欄位送出,也會自動填入同名的參數位置,無需額外加 P: 或 Q: 前綴:
# todoId 同時是 Body 和 Path 參數 → 自動帶入 P:todoId
| todoId | title |
| 123 | 買豆漿 |巢狀 JSON 建構
支援 dot notation 和陣列索引,自動建構深層 JSON:
# DataTable — 使用 dot notation 表達巢狀結構
| name | config.theme | tags[0] | tags[1] |
| 我的專案 | dark | work | urgent |JSON 格式可直接寫出巢狀結構,更直覺:
# JSON — 直接描述巢狀結構
"""json
{
"name": "我的專案",
"config": { "theme": "dark" },
"tags": ["work", "urgent"]
}
"""兩者產生相同的 Request Body:
{
"name": "我的專案",
"config": { "theme": "dark" },
"tags": ["work", "urgent"]
}Key 含 . 的逃脫語法
當 JSON key 本身包含 . 字元時(如 "btn.save"),使用 ["..."] bracket notation 避免被誤拆為多層巢狀:
# DataTable — 使用 bracket notation
| content.["btn.save"] | content.["btn.cancel"] |
| 儲存 | 取消 |JSON DocString 扁平化時會自動處理逃脫,使用者直接撰寫原始 key 即可:
# JSON — key 自然包含 ".",框架自動處理
"""json
{
"content": {
"btn.save": "儲存",
"btn.cancel": "取消"
}
}
"""["..."](字串 key 逃脫)與 [0](陣列索引)可共存於同一路徑中:
data.[0].["field.name"]
→ data 為物件 key、[0] 為陣列索引、["field.name"] 為含 "." 的物件 key頂層陣列 Body
當 API 的 request body 為頂層陣列(如批次建立),JSON DocString 可直接使用 [...] 作為頂層:
When (No Actor) 批次新增待辦事項, call JSON:
"""json
[
{ "title": "買牛奶", "priority": 1 },
{ "title": "寫報告", "priority": 2 }
]
"""需同時傳遞 Path/Query/Header 參數時,頂層維持 {},以 B: 標記 body 內容:
When (No Actor) 批次新增待辦事項, call JSON:
"""json
{
P"projectId": $project1.id,
Q"notify": "true",
B: [
{ "title": "買牛奶", "priority": 1 },
{ "title": "寫報告", "priority": 2 }
]
}
"""等價 DataTable:
| P:projectId | Q:notify | [0].title | [0].priority | [1].title | [1].priority |
| $project1.id | true | 買牛奶 | 1 | 寫報告 | 2 |B: 對物件 Body 也適用,提供「參數與 body 視覺分離」的選項(非前綴 key 的隱式 body 規則維持向後相容):
# 隱式 body(現行寫法,向後相容)
"""json
{ P"projectId": $project1.id, "title": "買牛奶", "priority": 1 }
"""
# 顯式 B: body(等價寫法)
"""json
{
P"projectId": $project1.id,
B: { "title": "買牛奶", "priority": 1 }
}
"""注意:
B:與非前綴的一般 key(如"name")不可共存——使用B:時,body 全部收納於B:值內。
裸值 Body
當 API 的 request body 接受原始型別(boolean、string、integer),使用 B: 標記裸值:
# DataTable — B: 前綴承載裸值
When 切換資源狀態, call table:
| P:id | B: |
| $res1 | true |
# JSON — B: 承載裸值
When (No Actor) 切換資源狀態, call JSON:
"""json
{ P"id": $res1, B: true }
"""
When (No Actor) 提交回饋, call JSON:
"""json
{ B: "這是一段回饋" }
"""呼叫者身份
(UID="...") 指定呼叫者身份,框架會自動注入對應的 Bearer Token:
# 使用變數作為呼叫者
When (UID="$Alice.id") 查詢待辦事項, call table:
| Q:status |
| active |
# 無需身份驗證
When (No Actor) 健康檢查, call table:
| |即使沒有參數,仍需提供空的 DataTable 以符合語法要求。
時間格式
ApiCall 中的時間值會根據 isa.yml 設定的 time_format 自動轉換:
| 格式 | 輸出範例 |
|---|---|
ISO(預設) | "2025-12-25T14:30:00+08:00" |
TIMESTAMP | 1766735400000 |
EPOCH | 1766735400 |
DATE_ONLY | "2025-12-25" |
TIME_ONLY | "14:30:00" |
錯誤處理
Lint 階段
| 錯誤 | 說明 |
|---|---|
| 欄位不存在 | DataTable 中的欄位名稱在 API Spec 找不到定義 |
| 缺少必填欄位 | OpenAPI 標記為 required 的參數未提供(可透過 @allow-missing-parameters 於特定情境放寬) |
| 擷取欄位不存在 | < 指定的回應欄位在 Response Schema 中找不到 |
@allow-missing-parameters:允許參數型前置條件
當你的測試目標是「驗證 API 會拒絕缺少/無效的必填參數」時,可在 Example(或 Scenario)加上 @allow-missing-parameters,讓 必填參數檢查 在該情境被跳過,但其他契約檢查仍維持啟用。
Feature: 查詢 API 參數驗證
Rule: 必填參數驗證規則
Example: 提供完整參數應該成功
When (UID="$Alice.id") 查詢資料, call table:
| Q:startDate | Q:endDate | Q:pageNo | Q:pageSize |
| 2024-01-01 | 2024-01-31 | 1 | 10 |
Then 查詢資料(200)回應, with table:
| success |
| true |
@allow-missing-parameters
Example: 缺少必填參數 startDate 應該失敗
When (UID="$Alice.id") 查詢資料, call table:
| Q:endDate | Q:pageNo | Q:pageSize |
| 2024-01-31 | 1 | 10 |
Then 查詢資料(400)回應, with table:
| errorCode |
| MISSING_REQUIRED_PARAMETER |何時使用
- 缺少 OpenAPI required 參數(例如少
Q:startDate) - 必填參數值無效,且你的後端會以「缺少/無效參數」類型錯誤回應
何時不要使用
- 業務規則驗證失敗(例如查詢區間超過 6 個月)
- 權限或認證失敗
- 資料狀態不符(例如重複申請)
- 參數完整且格式正確,但因業務邏輯被拒絕
執行階段
| 錯誤 | 說明 |
|---|---|
| API not found | summary 在 API Spec 中找不到對應操作 |
| 重複的 summary | 多個操作使用相同 summary,無法唯一識別 |
完整範例
Feature: 待辦事項管理
Background:
Given 現在時間為 "2026-01-27T10:00:00"
# EntitySetup 指令 — 準備測試資料(參見 EntitySetup 文件)
# >Alice.id / <userId:擷取回應中的 userId 存入 Alice.id 變數
Given 準備一個使用者, with table:
| >Alice.id | name | email | status |
| <userId | Alice | alice@example.com | ACTIVE |
Example: 新增並更新待辦事項
# (No Actor):無需身份驗證的 API 呼叫
# >AliceToken / <token:擷取回應中的 token
When (No Actor) 使用者登入, call table:
| >AliceToken | email | password |
| <token | alice@example.com | password123 |
# (UID="$Alice.id"):以 Alice 身份呼叫,自動注入 Bearer Token
When (UID="$Alice.id") 新增待辦事項, call table:
| >todo1.id | title |
| <todoId | 買牛奶 |
# P: 前綴表示 Path 參數;$todo1.id 引用先前擷取的變數
When (UID="$Alice.id") 更新待辦事項, call table:
| P:todoId | title |
| $todo1.id | 買豆漿 |
# ResponseValidate 指令 — 驗證回應(參見 ResponseValidate 文件)
Then 更新待辦事項(200)回應, with table:
| todoId | title | updatedAt |
| $todo1.id | 買豆漿 | &sameTime("2026-01-27T10:00:00") |