Skip to content
axxbetadocs
nextv0.2.26
GitHub
These are the docs of main, not released yet. See the docs of v0.2.26, the latest release.

Send REST requests

View .mdOpen in Claude

A scenario registers a REST service, adds requests to it, executes them and checks their responses (rest pack). This page is about what happens in between: the payload a request sends, the redirects it follows, the cookies it sends, and the headers a custom step gives it. To sign a request as a webhook is signed, or authorize it with a token, see Test webhooks and tokens.

The REST pack tests APIs. To test a web app’s pages, forms or sign-in, use the web-core pack: a real browser keeps cookies, follows redirects and submits forms, as the app’s users do (Test web apps).

A payload starts from an example of the service’s OpenAPI specification, from an empty template the payload property steps fill, or from a file of the project, sent as it is:

Scenario Outline: A shop's order system registers the parcels it exports
Given a POST request to /api/parcels
And a request payload using an application/json requests/<order>.json resource
When the request is executed
Then the response status code is 201
And the response header Location is '/api/parcels/<reference>'
Examples:
| order | reference |
| order-7731 | PX-REG-1301 |
| order-7732 | PX-REG-1302 |
  • The file is found like the other files steps name: relative to the resources directories, or to axx.yaml’s directory.
  • A JSON payload can still change. The payload property steps edit it, like a payload from an example. A property inside an object the payload lacks creates the object: recipient.name and recipient.postcode fill an empty template’s recipient (ADR 0010).
  • A form payload file is a JSON object. With application/x-www-form-urlencoded, its properties are sent form-encoded.
  • No OpenAPI specification is needed, which suits a callback or a webhook that the service has no contract for.

A fixture factory keeps such files in one place and checks them against a schema. The parcels example generates the two files above with requests/registrations.factory.yaml, from the ParcelRequest schema of the service’s OpenAPI contract.

Requests are numbered in the order a scenario adds them. The first is the default, which steps without an ordinal use; every other request is added, executed and checked with its ordinal:

Scenario: A registered parcel can be looked up by its reference
Given a POST request to /api/parcels
And a request payload using an application/json content example
And the request payload property reference is 'PX-REG-1401'
And a 2nd ordered GET request to /api/parcels/PX-REG-1401
When the request is executed
And the 2nd ordered request is executed
Then the response status code is 201
And the 2nd ordered response status code is 200
And the response payload property reference is 'PX-REG-1401' for 2nd ordered response
  • Add a request before using it: a step that uses the 2nd request fails unless a 2nd was added before it. axx validate says so, with the step that adds it.
  • A second a GET request to … adds the 1st again, and fails: write it as a 2nd ordered GET request to …. axx validate shows the line to write.
  • A check names its response: the 2nd ordered response status code is 200, or … for 2nd ordered response at the end of the other response steps. A check without one reads the 1st response.

A property can pick items out of a list with a JSONPath filter: parcels[?(@.reference=='PX-REG-1401')].status is the status of that parcel. The value a check compares is the list of what matched, so one match reads '["delivered"]'.

Then the response payload property parcels[?(@.reference=='PX-REG-1401')].status is '["delivered"]'
And the response payload property parcels[?(@.service=~/Next\sday\sair/)].reference is '["PX-REG-1401"]'
  • A path is one word, without spaces. To match a text with spaces, use =~ and a regular expression, where \s stands for the space.
  • =~ matches the whole text, as Jayway’s JSONPath does: /Next\sday/ doesn’t match “Next day air”, while /Next\sday.*/ does.
Request Redirects it follows The response the steps check
GET, HEAD 301, 302, 303, 307 and 308 the one the redirects lead to
POST, PUT, PATCH, DELETE, … 303 See Other, with a GET after a 303, the one it leads to
POST, PUT, PATCH, DELETE, … none of the others: 301, 302, 307 and 308 the redirect itself

A redirect the pack follows is not kept: its status, its Location header and the cookies it sets are not the response’s, and the response steps cannot check them. The scenario’s log shows the request as the step sent it and the status at the end, like GET http://localhost:8080/api/parcels/PX-REG-1010 -> 200 OK (12ms, 412 bytes).

The GET that follows a 303 has no body and no Content-Type. It has the request’s other headers, Authorization and Cookie only when the redirect stays on the same host or goes to a subdomain of it.

A POST, PUT, PATCH or DELETE answered with 301, 302, 307 or 308 has the redirect as its response. Check its status and its Location header, and send the request it leads to as the next ordered request. Say a service moved its API from /api/v1 and answers the old paths with 308 Permanent Redirect:

Scenario: A cancellation sent to the old API cancels the parcel through its redirect
Given a 1st ordered DELETE request to /api/v1/parcels/PX-REG-1010
And a 2nd ordered DELETE request to /api/parcels/PX-REG-1010
When the 1st ordered request is executed
And the 2nd ordered request is executed
Then the 1st ordered response status code is 308
And the response header Location is '/api/parcels/PX-REG-1010' for 1st ordered response
And the 2nd ordered response status code is 204

The scenario sends both requests, then checks both responses: the redirect, where it leads, and the cancellation there.

The REST pack keeps no cookies. A Set-Cookie response header is never sent back: not with the scenario’s later requests, and not to where a redirect leads. A request carries the cookies its scenario gives it, in its Cookie header (the request header Cookie is '...'), and nothing else, so scenarios running side by side never share cookies.

A custom step changes a request the pack’s steps built, before it is executed, through the request’s headers: Header() returns the headers it is sent with, and Set, Add and Del change them. SetHeader(name, value) does what the request header steps do. Write custom steps has an example.