# Assertions Source: https://hitspec.dev/concepts/assertions Validate HTTP responses with 26 assertion operators covering equality, strings, types, arrays, schemas, and snapshots. Assertions let you validate every aspect of an HTTP response -- status codes, headers, body content, response times, and more. hitspec provides 26 operators grouped into six categories. ## Assertion Block Syntax Wrap assertions in `>>>` and `<<<` markers: ```http theme={null} GET {{baseUrl}}/users >>> expect status 200 expect body type array expect body[0].id exists expect duration < 500 <<< ``` Each line inside the block follows this pattern: ``` expect ``` When the operator is `==`, you can omit it, as long as the value starts like a value (a number, a quoted string, `true`, `false`, `null`, `[` or `{`): ```http theme={null} >>> expect status 200 # shorthand for: expect status == 200 expect status == 200 # explicit form <<< ``` A trailing `# comment` is allowed on any line (the `#` must follow whitespace and be followed by whitespace or the end of the line). Blank lines and lines starting with `#` or `//` inside the block are ignored. Any other line that does not start with `expect` is an error. ### Mistakes are errors, not guesses hitspec refuses assertion lines it would otherwise have to guess at. `hitspec run` and `hitspec validate` report them with `file:line:column` and exit with code 2: ``` api.http:6:18: unknown operator "contians" (did you mean "contains"?) ``` * An unknown operator (`contians`, `=`, `===`, `equals`) is an error. A bare word after the subject is never read as "== word": write `expect body.name == Jane` or `expect body.name "Jane"`. * An operator without a value (`expect body.name ==`) is an error. It no longer reads the next line as its value. * Extra values are an error: `expect status 200 201` fails with a hint to write `expect status in [200, 201]`. * `expect` misspelt (`expcet status 200`) is an error, so a line can never silently assert nothing. ### Expected Values Every operator that takes a value reads it with one rule. The value is either: 1. a **JSON literal**: a string (`"Jane Doe"` or `'Jane Doe'`), a number, `true`, `false`, `null`, a list `[1, "a", b]`, or an object `{"a": 1}`; or 2. a **regular expression** `/pattern/flags` after `matches` (flags `i`, `m`, `s`, `U`; `g` is accepted and ignored); or 3. otherwise, **the rest of the line, trimmed, exactly as written**, as a string. ```http theme={null} >>> expect body.name == Jane Doe # the string "Jane Doe", no quotes needed expect body.path == ./files/x # ./files/x, $HOME, ^start, @handle, #fff are all fine expect body.msg == "line\nbreak" # JSON escapes work inside quotes expect body.address == {"city": "Oslo", "zip": "0150"} expect status in [200, 201] <<< ``` Quoted strings use JSON escapes; if the text is not valid JSON (a regex such as `"\d+"`), it is used as written. In a list, each element follows the same rule. `{{variable}}` references are filled in before comparing. Text after a closing quote is an error (`"Jane" Doe`), so quote the whole value or none of it. For `contains`, `!contains`, `startsWith` and `endsWith` the value is always text: a quoted string is decoded, anything else is used exactly as written (`expect body startsWith {`, `expect body.price contains 1.50`). ## Assertion Subjects | Subject | Description | Example | | - | - | - | | `status` | HTTP status code | `expect status 200` | | `duration` | Response time in milliseconds | `expect duration < 1000` | | `header ` | Response header value | `expect header Content-Type contains json` | | `body` | Full response body (parsed when JSON, text otherwise) | `expect body contains "success"` | | `body.` | Path into a JSON body (gjson syntax: `.#` counts, `.#(...)` queries) | `expect body.user.name == "John"` | | `body[n]` | Array index | `expect body[0].id exists` | | `jsonpath $.` | JSONPath expression (`$.a.b`, `$.a[0]`, `$.a[*].b`) | `expect jsonpath $.users[0].id exists` | A body path needs a JSON response: `application/json`, `text/json` and any `+json` type such as `application/problem+json`, with or without `; charset=...`. On any other response, `body.x` fails with "response body is not JSON" instead of quietly matching; only the whole `body` can be read. See [Assertion Subjects](/reference/assertion-subjects). *** ## Equality and Comparison Compare values using standard comparison operators. | Operator | Syntax | Description | | - | - | - | | `==` | `expect status == 200` | Equals | | `!=` | `expect body.error != null` | Not equals | | `>` | `expect body.count > 0` | Greater than | | `>=` | `expect body.count >= 1` | Greater than or equal | | `<` | `expect duration < 1000` | Less than | | `<=` | `expect duration <= 500` | Less than or equal | ```http theme={null} ### Verify response timing and pagination GET {{baseUrl}}/users?page=1&limit=20 >>> expect status == 200 expect duration < 500 expect body.total > 0 expect body.page == 1 expect body.perPage >= 20 <<< ``` Use `expect status 200` as shorthand for `expect status == 200`. The `==` operator is implied when omitted. `==` and `!=` compare objects and arrays **deeply**, as JSON: key order does not matter, `1` equals `1.0`, and `["a b"]` is not `["a", "b"]`: ```http theme={null} >>> expect body.address == {"city": "Oslo", "zip": "0150"} expect body.ids == [1, 2, 3] <<< ``` Scalars are compared loosely: `"42"` and `42` are equal. `null` only equals `null` (see [null versus absent](#null-versus-absent)). *** ## String Operators Match, search, and compare string values. | Operator | Syntax | Description | | - | - | - | | `contains` | `expect body contains "success"` | Contains substring | | `!contains` | `expect body !contains "error"` | Does not contain substring | | `startsWith` | `expect body.url startsWith "https"` | Starts with prefix | | `endsWith` | `expect body.email endsWith ".com"` | Ends with suffix | | `matches` | `expect body.id matches /^\d+$/` | Matches regular expression | ```http theme={null} ### Check user profile fields GET {{baseUrl}}/users/1 >>> expect status 200 expect body.email endsWith "@example.com" expect body.website startsWith "https" expect body.bio !contains "