DEV Community

Cover image for Performance Testing Series — Part 2: Building Realistic User Journeys with Samplers & Listeners
Faizal
Faizal

Posted on

Performance Testing Series — Part 2: Building Realistic User Journeys with Samplers & Listeners

Performance Testing Series — Part 2: Building Realistic User Journeys with Samplers & Listeners

"A load test that hits one endpoint in a loop isn't a user journey. It's a hammer."

In Part 1, we got JMeter running and fired a single GET request at the ReqRes API. That was the foundation — the mechanics of Thread Groups, Samplers, and Listeners.

But real users don't hit one endpoint and disappear.

They log in, then browse, then fetch specific records, then update something. The load your system experiences is a sequence of actions, not a single repeated call.

In Part 2, we build exactly that. A realistic, multi-step user journey — and the right Listeners to understand what's happening across the whole flow.


🎯 What Is a "User Journey" in Load Testing?

Think of it this way.

A single endpoint test tells you: "Can this API handle 100 concurrent GET requests?"

A user journey test tells you: "Can this system handle 100 concurrent users doing what real users actually do?"

Those are different questions — and they produce different load patterns on the server.

For our ReqRes-based series, the journey we'll simulate is:

Step 1 → List Users      (GET /api/users?page=2)
Step 2 → Get Single User (GET /api/users/2)
Step 3 → Login           (POST /api/login)
Enter fullscreen mode Exit fullscreen mode

Three steps. Different HTTP methods. Different endpoints. One Thread Group simulating all three in sequence — exactly the way a real user would move through a system.


🧱 Understanding Samplers — The Building Blocks

A Sampler is a single request in JMeter. Every time a virtual user executes a Sampler, JMeter sends a real HTTP call and records the result.

You already used one in Part 1: the HTTP Request sampler.

Here's what else you'll encounter as you go deeper:

Sampler What it does
HTTP Request Sends HTTP/HTTPS calls — what we use for REST APIs
JDBC Request Queries a database directly
FTP Request Tests FTP servers
SMTP Sampler Sends emails
TCP Sampler Raw TCP socket communication

For this series, we stay with HTTP Request throughout — it covers everything a REST API performance test needs.

What matters most inside the HTTP Request sampler:

Protocol        → http or https
Server Name     → reqres.in
Port            → usually blank for standard HTTPS (443 implied)
Path            → /api/users?page=2
Method          → GET, POST, PUT, DELETE
Body Data       → JSON payload for POST/PUT requests
Enter fullscreen mode Exit fullscreen mode

👂 Understanding Listeners — How You See What's Happening

A Listener collects and displays results from your test. JMeter has many — and choosing the wrong one is one of the most common beginner mistakes.

Here's the honest breakdown:

Listener Use it for Warning
View Results Tree Debugging — inspect individual requests/responses Never use during real load runs — kills performance
Aggregate Report Core metrics per sampler: avg, min, max, throughput, error % Best for post-run analysis
Summary Report Lighter version of Aggregate — overall numbers Good for quick CI/CD output
Response Time Graph Visual trend of response times over the test duration Good for spotting degradation mid-run
Active Threads Over Time See how virtual users ramp up and down Useful for validating ramp-up config

The rule: use View Results Tree only when building and debugging. Switch to Aggregate Report or Summary Report when you're running real load. We'll go deep on reading these numbers in Part 6 — for now, just know which tools to reach for.


🏗️ Building the Multi-Step User Journey

Let's extend the test plan from Part 1 into a proper three-step flow.

Open JMeter. You should have the test plan from Part 1 — the one Thread Group with the HTTP Header Manager and the single GET sampler.

We're going to add two more samplers to complete the journey.

Step 1 — Rename Your Existing Sampler

Click on your existing HTTP Request sampler. Rename it to "List Users" so it's clear what it does in the tree.

Confirm it's configured as:

Protocol: https
Server Name: reqres.in
Path: /api/users?page=2
Method: GET
Enter fullscreen mode Exit fullscreen mode

Step 2 — Add the "Get Single User" Sampler

Right-click the Thread GroupAddSamplerHTTP Request

Configure it:

Name: Get Single User
Protocol: https
Server Name: reqres.in
Path: /api/users/2
Method: GET
Enter fullscreen mode Exit fullscreen mode

Step 3 — Add the "Login" Sampler

Right-click the Thread GroupAddSamplerHTTP Request

Configure it:

Name: Login
Protocol: https
Server Name: reqres.in
Path: /api/login
Method: POST
Enter fullscreen mode Exit fullscreen mode

For the POST body, click the Body Data tab and paste:

{
  "email": "eve.holt@reqres.in",
  "password": "cityslicka"
}
Enter fullscreen mode Exit fullscreen mode

Then click the HTTP Header Manager (already in your Thread Group from Part 1) and add a second header:

Name: Content-Type
Value: application/json
Enter fullscreen mode Exit fullscreen mode

This tells the API we're sending JSON — without it, the POST will likely return a 400 or be misread server-side.

Step 4 — Add an Aggregate Report Listener

Right-click the Thread GroupAddListenerAggregate Report

Keep the View Results Tree too for now — you're still in build/debug mode. We'll remove it before running serious load.

Step 5 — Run It

Hit Start (Ctrl+R).

Watch the View Results Tree populate. You should see three entries — one per sampler — each with a green checkmark:

✅ List Users      → 200 OK
✅ Get Single User → 200 OK
✅ Login           → 200 OK
Enter fullscreen mode Exit fullscreen mode

If Login returns 400, double-check the Content-Type header and the JSON body — that's almost always the culprit for POST failures at this stage.


📊 Reading the Aggregate Report

Once the run finishes, click the Aggregate Report listener. You'll see one row per sampler:

Label           | Samples | Avg | Min | Max | Error % | Throughput
List Users      |   10    | 210 | 180 | 340 |  0.00%  | 1.8/sec
Get Single User |   10    | 190 | 165 | 290 |  0.00%  | 1.9/sec
Login           |   10    | 230 | 200 | 380 |  0.00%  | 1.7/sec
Enter fullscreen mode Exit fullscreen mode

What each column means:

  • Samples — how many times this request was fired (threads × loops)
  • Avg — average response time in milliseconds across all samples
  • Min / Max — fastest and slowest individual responses
  • Error % — percentage of requests that returned a non-2xx response
  • Throughput — requests per second this sampler achieved

We'll go much deeper on interpreting these numbers in Part 6. For now, the key thing to verify: Error % is 0.00% across all three. If it isn't, the journey has a broken step — fix it before adding more load.


🧩 Why Sequence Matters

JMeter executes samplers top to bottom within a Thread Group, in order, for each virtual user. This means each simulated user does exactly:

→ List Users
→ Get Single User
→ Login
→ (repeat if Loop Count > 1)
Enter fullscreen mode Exit fullscreen mode

This sequential execution is what makes it a journey, not just parallel hammering of three separate endpoints. In Part 3, we'll make this even more realistic — extracting the user ID from the List Users response and passing it dynamically into Get Single User, instead of hardcoding /api/users/2.

That's where correlation comes in. And it's what separates a real load test from a toy one.


🗂️ What Your Test Plan Should Look Like Now

Test Plan
└── Thread Group
    ├── HTTP Header Manager      ← x-api-key + Content-Type
    ├── List Users               ← GET /api/users?page=2
    ├── Get Single User          ← GET /api/users/2
    ├── Login                    ← POST /api/login
    ├── View Results Tree        ← for debugging
    └── Aggregate Report         ← for analysis
Enter fullscreen mode Exit fullscreen mode

Clean, readable, and structured exactly the way a maintainable JMeter test plan should be.


🚀 Series Roadmap

Part 1 — JMeter Setup & Your First Load Test                  ✅ Done
Part 2 — Building Realistic User Journeys                     ← You are here
Part 3 — Parameterization & Correlation: Making Tests Dynamic
Part 4 — Assertions & Response Validation
Part 5 — Load Patterns: Ramp-Up, Spike, Soak & Stress Testing
Part 6 — Reading the Numbers: Throughput, Latency & Percentiles
Part 7 — Distributed & Realistic Load Simulation
Part 8 — Automating Performance Tests in CI/CD with GitHub Actions
Enter fullscreen mode Exit fullscreen mode

🔖 Before You Go

One endpoint in a loop is a stress test for a single route.

A chained user journey is a stress test for your system — the way it actually gets used.

That distinction matters more than almost anything else in performance testing. And now you're building tests that reflect it.


Follow me so you don't miss Part 3 — where we make this journey fully dynamic with parameterization and correlation. No more hardcoded IDs.

Drop a comment below 👇

  • Did your Login POST return 200 on the first try — or did you hit the 400?
  • Are you testing a public API, or already thinking about applying this to your own systems?
  • What step of the journey surprised you most?

All levels welcome. Let's keep building. 🙌


Faizal Shaikh | Senior Automation Engineer | Performance & AI Testing
Connect with me on LinkedIn

Top comments (0)