Shop API lab
A small shop: sign in, browse products, place and cancel orders. This lab covers the things real API testing is made of: tokens, requests that depend on each other, one request run with many inputs, and load.
Level: intermediate · Time: about 45 minutes · Before this: Todo API lab
Download
- shop-api.mockstudio.json — the mock API, for Mock Studio
- shop-api.apiworkbench.json — the requests and tests, for ApiWorkbench
- shop-orders.csv — five rows of test data
Set up
In Mock Studio, select Import, choose Mock Studio project file and pick
shop-api.mockstudio.json. Select Start server. The mock is athttp://localhost:4020.In ApiWorkbench, select Import, choose
shop-api.apiworkbench.jsonand select Import.
The API
| Method | Path | Needs a token | Answers |
|---|---|---|---|
| POST | /auth/login | No | 200 with a token, or 401 |
| GET | /me | Yes | 200, or 401 |
| GET | /products | No | 200. ?category=books filters |
| GET | /products/:id | No | 200, or 404 for id 999 |
| POST | /orders | Yes | 201, 400 (fields missing), 422 (quantity over 10), or 401 |
| GET | /orders/:id | Yes | 200, or 401 |
| DELETE | /orders/:id | Yes | 204, or 401 |
The only account is ada@example.com with the password secret123. Both are made up and exist only in this mock.
Part 1: sign in and carry the token
Open Auth › Sign in and look at the Body tab:
json{ "email": "{{email}}", "password": "{{password}}" }The values come from collection variables, so the credentials are written in one place.
Open the Captures tab:
texttoken = body.tokenSelect Send. The response has a
token, and the capture stores it in a variable namedtoken.Open Auth › Who am I and look at the Auth tab: the type is Bearer Token and the token is
{{token}}.Select Send. You get 200 and Ada's profile.
Now open Who am I, without a token and send it. 401. The same endpoint, the same URL; the only difference is the missing Authorization header. That request is a test in its own right: it proves the endpoint is protected.
Order matters
Requests that use {{token}} only work after Sign in has run. In the runner that happens by itself, because Sign in comes first. See Captures.
Part 2: how the mock decides
In Mock Studio, open Auth › Login. It uses Conditional rules, with two responses:
| Response | Conditions | Status |
|---|---|---|
| Signed in | Body field email equals ada@example.com and password equals secret123 | 200 |
| Wrong email or password | None: the fallback | 401 |
Responses are checked top to bottom. The first whose conditions all match is sent; a response with no conditions is the fallback.
Orders › Create order has three, and their order matters:
| Response | Conditions | Status |
|---|---|---|
| Quantity too large | quantity greater than 10 | 422 |
| Created | productId exists and quantity exists | 201 |
| Fields missing | None: the fallback | 400 |
If Created came first, an order for 50 would match it and never reach the 422 rule. Put the most specific rule first. See Conditional rules.
The token requirement is separate: Create order has Require auth header turned on, so any request without an Authorization header gets a 401 before the rules are looked at.
Part 3: run the whole journey
Open the collection's ⋯ menu, choose Run collection, and select Run. Thirteen requests run in order and all pass.
Follow the thread through the Orders folder:
- Place an order creates an order and captures
orderId = body.id. - Check the order reads
/orders/{{orderId}}. - Cancel the order deletes
/orders/{{orderId}}.
Three requests, two captured values (token and orderId), one user journey. That is a workflow test: see Workflow testing.
Part 4: one request, many inputs
Place an order sends this body:
{
"productId": {{productId}},
"quantity": {{quantity}}
}Normally productId and quantity come from the collection variables (1 and 2). In a data-driven run they come from a file instead, one row per pass. Open shop-orders.csv:
productId,quantity,expectedStatus
1,1,201
2,3,201
3,10,201
1,11,422
2,50,422Five orders: three that should succeed, and two that are over the limit of 10 and should be refused. Row 3 and row 4 sit either side of the limit on purpose. That is boundary testing.
Open the ⋯ menu of the Orders folder and choose Run folder.
In the runner, select Load data file and pick
shop-orders.csv. The preview shows five rows.Select Run. The folder runs five times, once per row.
The first pass needs a token
Run Auth › Sign in once before running the Orders folder by itself, so that token has a value. Or run the whole collection with the data file.
How does the test know that row 4 should get a 422? A no-code test has one fixed expected value, so this request uses a short script instead. Open Place an order › Scripts, and look at the post-response script:
const expected = Number(pm.iterationData.get('expectedStatus') || 201);
pm.test('status is ' + expected, function () {
pm.response.to.have.status(expected);
});It reads expectedStatus from the current row and checks the response against it. Without a data file there is no row, so it expects 201. See Data-driven runs and Scripts.
Try it: add a row 1,0,201 to the CSV and run again. The mock accepts a quantity of zero. Should a real shop? You have found a missing rule, which is what boundary tests are for.
Part 5: load the mock with a flow
The Mock Studio project includes a flow: a journey run by several virtual users at once.
In Mock Studio, open Flows on the rail and select Checkout journey.
Read its four steps: Sign in, Browse products, Place an order, Check the order. The first extracts
token = body.token; later steps sendAuthorization: Bearer {{vars.token}}.Note the load profile: 5 virtual users, 10 iterations each, a 2 second ramp-up and 100 ms of think time. That is 200 requests.
Select Run flow.
The results show requests per second, the error rate, and p50, p95 and p99 response times for each step. Against a mock on your own machine they will be a few milliseconds; the point is to learn to read them. Load testing explains what the percentiles mean.
To aim the same journey at a real server later, set the flow's Target base URL. Only load-test systems you own or have permission to test.
You can do the same from ApiWorkbench: open the collection's ⋯ menu and choose Load test. See Load testing in ApiWorkbench.
What you learned
- Capture a token at sign-in and send it as a Bearer token.
- A request without the token is itself a test.
- In conditional rules, order decides which response wins.
- A data file runs one request with many inputs, including the edges.
- A flow runs a whole journey with many users at once.
Next steps
- Flaky Payments lab: when the API misbehaves
- Testing authentication
- Data-driven testing
- Run it in CI