Flows: load and user journeys
A flow is a list of requests, called steps, that Mock Studio sends for you: log in, fetch something, create something. One virtual user runs the steps in order like a person using an app. Many virtual users run them at the same time to produce load. While a flow runs you see how fast and how reliably the server answers.
Use flows to:
- check a whole journey through your mock in one click, instead of calling endpoints one by one;
- see how a flaky or slow mock behaves under many calls;
- send realistic concurrent traffic to another server you own, by setting a target URL.
Create a flow
Select Flows in the rail.
On the No flows yet screen, choose one of:
- Build from endpoints makes a flow called All endpoints with one step for every enabled endpoint.
- Blank flow makes a flow with a single
GET /healthstep for you to edit.
Type a name in Flow name, such as
Checkout journey.
Later, use the New flow button in the list of flows, which offers Blank flow and From all endpoints. Each flow's menu has Duplicate and Delete. Flows are saved in the project file with your endpoints.
When a flow is built from endpoints, each step is made callable: a path parameter such as :id becomes {{vu}}, * becomes sample, POST, PUT and PATCH steps get a small JSON body, and endpoints that require auth get a header with the value Bearer test.
Steps
The Steps section lists the requests one virtual user makes in a round. One round through all the steps is called an iteration.
Select Add step to add a step from one of your endpoints, or Custom request for a blank step. Each step has a row of main fields and a details area for the name, headers, body and extract, which you open with the button at the end of the row.
| Field | What it is | Example |
|---|---|---|
| Checkbox | Include this step in runs. Untick it to skip the step without deleting it. | |
| Method | GET, POST, PUT, PATCH, DELETE, HEAD or OPTIONS | POST |
| Path | Added to the target URL. A full http:// or https:// URL is used as it is. | /orders/{{vars.orderId}} |
| Expect | Status codes that count as success | 200 |
| Step name (optional) | Shown in the results. Empty uses the method and path. | Log in |
| Headers | One per line, as Name: value | Authorization: Bearer {{vars.token}} |
| Extract into variables | One per line, as name = source | token = body.token |
| Body | Data sent with the request | {"email": "{{faker.email}}"} |
Use Move up, Move down and Remove step to arrange the list.
A body that starts with { or [ is sent as application/json, and any other body as text/plain, unless Headers sets a Content-Type.
Expect: which statuses count as success
| You type | Success means |
|---|---|
| nothing | Any status below 400 |
200 | Exactly 200 |
2xx | Any status from 200 to 299 |
200,201,404 | Any of the listed codes |
2xx,404 | Any 2xx status, or 404 |
Any other status makes the step failed. So does a timeout, which is 30 seconds with no reply, or a connection error.
To test an error case on purpose, expect the error: a step that should be rejected can have Expect 401.
Extract: keep a value for later steps
Extract into variables saves part of a step's reply under a name. Write one line per value, as name = source.
| Source | Reads | Example line |
|---|---|---|
body.<path> | A field of the JSON reply | token = body.token |
body.<path> with a number | An item of a list, counting from 0 | id = body.items.0.id |
header.<name> | A response header | etag = header.etag |
status | The status code | code = status |
A later step uses the value as {{vars.name}}, for example {{vars.token}}. Extracted values are reset at the start of every iteration, and each virtual user has its own.
If a value cannot be found, the variable is not set, and {{vars.name}} is sent as typed.
Tokens in steps
Path, Headers and Body can use these tokens:
| Token | Replaced with |
|---|---|
{{vars.<name>}} | A value extracted by an earlier step in the same iteration |
{{vu}} | The virtual user's number, starting at 1 |
{{iteration}} | The round number for that user, starting at 1 |
{{uuid}}, {{now}}, {{timestamp}}, {{randomInt(1,9)}}, {{faker.name}} and the other generated-value tokens | A fresh value for each request |
{{vu}} gives each user a different value, for example /users/{{vu}} or an email of user{{vu}}@example.com. The request tokens used in responses, such as {{params.id}} and {{body.email}}, have no meaning in a step and are left as typed. See Dynamic templating for the generated-value tokens.
Load profile
The Load profile section sets how much traffic to send.
| Setting | Default | What it does |
|---|---|---|
| Virtual users | 5 | How many simulated users run the steps at the same time, from 1 to 1000. |
| Iterations / user | 10 | How many rounds each user makes. Ignored when a duration is set. |
| Duration (s) | 0 | Run for this many seconds instead of a number of iterations. 0 means use iterations. |
| Ramp-up (s) | 0 | Spread user start times over this many seconds instead of starting everyone at once. |
| Think time (ms) | 0 | A pause after each step, like a person reading a page. |
| Target base URL | empty | The server to call. Empty means this project's mock. |
Iterations or duration. With iterations, the total number of requests is known in advance:
requests = virtual users × iterations × enabled steps
5 users × 10 iterations × 3 steps = 150 requestsWith a duration, users repeat the steps until the time is up. The ramp-up time counts toward the duration.
Ramp-up. With 10 users and a ramp-up of 10 seconds, one new user starts each second. Use it to watch how the server copes as load grows, instead of hitting it with everyone at once.
Think time. In One by one mode the pause comes after every step. In All at once mode it comes after each round.
Step mode
| Mode | How one iteration runs | Use it for |
|---|---|---|
| One by one | Steps run in order, each waiting for the previous reply. Later steps can use extracted values. | User journeys: log in, then use the token |
| All at once | Every step of the round is sent at the same moment. Extracted values are not passed between steps. | Bursts of parallel calls, like a page that loads several things together |
Stop iteration on first failed step applies to One by one mode and is on by default. When a step fails, the user skips the rest of that round and starts the next one. Keep it on when later steps depend on an earlier one: if login fails, there is no token for the next step.
Target base URL
Leave Target base URL empty to run the flow against this project's mock at http://127.0.0.1: and its port. If the server is stopped, Run flow starts it for you.
Type an address, such as http://localhost:3000, to send the flow to another server. Each step's path is added to it.
WARNING
A flow with many users sends a lot of traffic. Only target servers you own or have permission to load-test. Sending load at someone else's service can get your address blocked and may break their terms of use.
Worked example: a login journey
This flow uses the sample project My First Mock API. Each user logs in, reads a user and creates a user.
Set One by one, 5 Virtual users and 10 Iterations / user, then add three steps:
| Step | Method and path | Details |
|---|---|---|
| Log in | POST /login | Body {"password": "secret"}, Expect 200, Extract token = body.token |
| Get user | GET /users/{{vu}} | Headers Authorization: Bearer {{vars.token}}, Expect 200 |
| Create user | POST /users | Body {"name": "{{faker.name}}", "email": "{{faker.email}}"}, Expect 201 |
Select Run flow. The run sends 5 × 10 × 3 = 150 requests. The first step captures the token the mock returns, and the second sends it back in a header.
Now break it on purpose: change the body of Log in to {"password": "wrong"} and run again. The login step gets 401, which is not in its Expect, so it fails. With Stop iteration on first failed step on, the other two steps are skipped: the results show 50 calls, all failed, with the error Unexpected status 401.
Run and stop
Select Run flow to start. Select Stop to end early: requests already in flight finish, and the results so far stay on screen. A status line shows how long the run has taken and how many iterations are done, then Finished or Stopped at the end.
Read the results
The Results section updates live. The numbers reset when you run the flow again and are not saved.
Summary
| Tile | Meaning |
|---|---|
| Requests | Total requests sent so far, including failed ones. |
| Req / sec | Throughput: requests finished per second, on average, since the run started. |
| Failed | Failed requests and the error rate as a percentage. |
| p50 | The median response time. |
| p95 | 95% of requests were faster than this. |
| p99 | 99% of requests were faster than this. |
| Avg | The mean response time. |
| Active users | Virtual users running right now. It climbs during ramp-up and drops to 0 at the end. |
What p50, p95 and p99 mean
These are percentiles. Line up every response time from fastest to slowest:
- p50, the median, is the one in the middle. Half the requests were faster and half slower. It is the typical response time.
- p95 is the time that 95% of requests beat. Only the slowest 1 in 20 took longer. It is a good guide to what most users feel.
- p99 is the time that 99% of requests beat. Only 1 in 100 took longer. It shows the worst delays a few users hit.
Example. A run reports p50 12 ms, p95 80 ms and p99 250 ms. A typical request took 12 ms. Nineteen in twenty finished within 80 ms. One in a hundred took a quarter of a second or more.
The average can hide slow requests. One 5-second reply among 99 fast ones barely moves the average but shows up at once in p99. Compare Avg with p50 and p95: when the average is well above the median, a few requests are much slower than the rest.
Percentiles are measured to the nearest millisecond below one second, and in coarser steps above that.
Throughput chart
Throughput (requests / second) draws one bar per second of the run, split into OK and Failed. Hover a bar for the exact numbers. The chart shows the last 2 minutes.
Per step
The Per step table gives the same numbers for each step, so you can spot which endpoint is slow or failing.
| Column | Meaning |
|---|---|
| Step | The step's name, or its method and path. |
| Calls | Requests sent for this step. |
| Failed | Calls with an unexpected status, a timeout or a connection error. |
| Avg, p50, p95, p99 | Response times in milliseconds. |
| Max | The slowest single call. |
Under each step you also see how many calls got each status code, such as 200 ×1,500, and the reasons calls failed, such as Unexpected status 401 (×210). Up to five different error messages are kept per step.
Common error messages
| Message | Meaning |
|---|---|
Unexpected status 401 | The reply's status is not in the step's Expect. |
Timed out after 30s | No reply within 30 seconds, for example from a Hang fault. |
Connection failed: … | The server could not be reached or dropped the connection. |
This flow has no enabled steps. | Every step is unticked, or there are none. |
Things to keep in mind
- The mock and the load come from the same computer. The numbers describe your mock on your machine, not the speed of the real API it stands in for.
- Delays and faults show up in the results. A response with an 800 ms delay gives a p50 near 800 ms. That makes flows a quick way to confirm that a latency or fault setting does what you intended.
- The Live log keeps 500 requests. During a large run, the Live section shows only the latest ones.
- Sequence responses are shared. An endpoint using Sequence (round-robin) has one counter for all virtual users, so users take turns through the same sequence.
For the ideas behind load numbers, see Load testing. For journeys with assertions on real APIs, see Workflow testing and the ApiWorkbench runner.