Test a response
A request can carry tests: assertions evaluated against its own response on every send.
The same assertion shape is used by a chain’s assert node, and one evaluator in the engine
serves both.
Write one
Section titled “Write one”The Form view’s Tests tab edits them. In YAML:
- name: Create user method: POST url: "{{baseUrl}}/users" body: contentType: application/json content: '{"email":"a@example.com"}' tests: - source: status op: in expected: "200, 201" - source: body path: id op: exists - source: header path: content-type op: contains expected: application/json - source: duration op: lt expected: "500"The vocabulary
Section titled “The vocabulary”An assertion is a source, an optional path, an op, and an optional expected.
Sources
Section titled “Sources”source | What path means | Notes |
|---|---|---|
status | — (unused) | The numeric HTTP status |
body | A path into the body, e.g. data.items.0.id | Evaluated by the same code that backs chain extraction — wildcards, .. and XPath included, see Path expressions |
header | The header name, e.g. content-type | Matched case-insensitively, so Content-Type works too |
cookie | The cookie name, e.g. session | Read from the shared jar, not just this response’s Set-Cookie — the cookie a login set three requests ago is usually the one you want |
var | A variable name | From the active environment for a request test, from chain scope in an assert node |
duration | — (unused) | How long the send took, in milliseconds |
size | — (unused) | The response body’s size in bytes |
A source that is not there — a missing header, a path that addresses nothing — reports not present naming what it looked for, rather than silently comparing against an empty string.
Operators
Section titled “Operators”Eighteen is too many to read as a flat list, so the editor groups them — Equality, Text, Numeric,
Presence, Collection, Contract — and filters the list by source: a duration is never offered
contains, a status is never offered startsWith. Changing a row’s source snaps its operator to
one that can apply, instead of leaving a row that could never pass.
op | Meaning |
|---|---|
eq | Equals expected |
ne | Does not equal expected |
contains | expected occurs in the value |
notContains | expected does not occur — passes when the value is absent; nothing cannot contain something |
startsWith | The value starts with expected |
endsWith | The value ends with expected |
matches | The value matches the regular expression in expected |
gt / gte | Numerically greater than / at least expected |
lt / lte | Numerically less than / at most expected |
between | Numerically inside an inclusive range: 1..10 (or 1,10) |
exists | The value is present and not empty — expected unused |
isEmpty | Absent, "", [] or {} all count as empty — expected unused |
length | Counts array items, object keys, or characters; expected is a count like 3, >=1, <10 or 1..5 |
in / notIn | The value is (not) one of 200, 201, 204 — [200, 201] is accepted too |
schema | The body matches the endpoint’s declared response schema — see below |
There is deliberately no neq beside ne and no general negate flag: two on-disk spellings of
one check is exactly what makes a committed format diff badly.
expected is a string, so quote a number in YAML (expected: "201"), and it may itself
contain {{variables}}. Write a regular expression as the pattern alone (^application/(json|problem\+json)),
not as a /…/flags literal. A numeric comparison against something that is not a number fails
with a message naming which side was not a number, rather than just failing.
Check the body against the spec
Section titled “Check the body against the spec”op: schema is the one assertion with nothing to type. It validates the response body against the
response the bound OpenAPI spec declares for the status that actually came back:
tests: - source: body op: schemaLeave expected blank for that. Set it to a declared status (400, default) to check a
different one — asserting that your error envelope matches the documented error shape, for example.
Four things worth knowing before you rely on it:
- It only applies to
source: body. Any other source reports that instead of quietly passing. - It needs a spec binding. With no declared response in scope it says so plainly — it never
passes by default. That is also why the operator is offered on a request’s Tests tab and not
in a chain’s
assertnode: a chain resolves its request nodes by id without carrying their endpoint bindings, so the option there would be dead. - A response the spec declares but gives no schema (a
204, say) holds — there is nothing to check. - For an array body only the first element is walked. That is enough to catch a reshaped item without an O(n) pass over a large collection.
Because the check reads the live spec rather than a pasted copy, it follows the API as it changes: nothing is snapshotted into your collection file.
Reading the results
Section titled “Reading the results”The response pane gets a pass/fail strip — “4 passed”, or “1 of 4 failed” — and a Tests tab
listing each assertion with what it expected and what it got. Run history stores results too, with
a passed/total pill on the row, so you can see when an endpoint started failing, not just that it
fails now.
What is not evaluated
Section titled “What is not evaluated”SSE and other streaming sends do not evaluate tests. A request whose Accept is
text/event-stream streams events rather than producing one buffered response, so there is nothing
for the assertions to run against. The pane says which checks it skipped rather than staying quiet
about it — silence there would be indistinguishable from everything passing. A streamed run is not
a pass; it is unchecked.
Keeping YAML clean
Section titled “Keeping YAML clean”An empty test list is dropped rather than written, so a request never gains tests: [] and
your diffs stay minimal.
In a chain
Section titled “In a chain”The assert node takes the same list, minus op: schema (above). All assertions passing routes to
the pass branch; any failure routes to fail — which means a chain can check its own
intermediate state and take a different path instead of just stopping — see
Build a chain. The shared cookie jar rides along, so a cookie source works
inside a chain exactly as it does for a request’s own tests.
- id: check-created type: assert position: { x: 400, y: 120 } assertions: - source: status op: eq expected: "201" - source: duration op: lt expected: "1000"Tests you did not write
Section titled “Tests you did not write”Importing a Postman collection turns the recognised half of its pm.* scripts into real test rows
rather than into more script: pm.response.to.have.status(200), .to.be.ok, .to.have.header(…)
and the pm.expect chains become assertions on the request, and pm.environment.set(…) becomes an
extraction — wired into a linear chain when a later request actually reads the value, and left out
when nothing does. Anything it does not recognise is preserved as a commented-out hook and named
in the import summary — a wrong translation would be a test that passes for the wrong reason, which
is worse than no translation. See Import from curl & Postman.
A note on scope
Section titled “A note on scope”Seven sources and eighteen operators is the whole vocabulary at v0.1.0-preview.10 — it grew from
four and seven in the previous release, and the earlier files still load unchanged. What is still
not here: op: schema inside a chain’s assert node, and any evaluation at all on a streaming
send.