Skip to content

Chapter 6 · Assertions and Failure Detection ​

Chapter goals: Master request-level assertions with catch_response=True, manually mark failures with resp.failure(), aggregate statistics with name=, and build reliable pass/fail logic.

6.1 Why Assertions Matter ​

By default, Locust only judges success/failure by HTTP status code: 2xx is success, 4xx/5xx is failure. But in production, "returning 200 with wrong data" is very common — for example, an API returns {"code": 500, "msg": "Internal error", "data": null} with HTTP status 200.

A load test without assertions is like a temperature check without a thermometer: you see all requests "succeed", but the system may already be broken.

python
from locust import HttpUser, task, between

class ApiUser(HttpUser):
    wait_time = between(1, 3)
    host = "https://api.example.com"

    @task
    def get_user(self):
        # Default behavior: only checks HTTP status code
        self.client.get("/api/users/1")
        # Status 200 → marked successful (even if body contains error info)

6.2 catch_response=True ​

Pass catch_response=True to self.client.get() / .post() and other methods — Locust pauses automatic marking, letting you decide success or failure manually:

python
from locust import HttpUser, task, between
import json

class ApiUser(HttpUser):
    wait_time = between(1, 2)
    host = "https://api.example.com"

    @task
    def get_user_with_assertion(self):
        with self.client.get("/api/users/1", catch_response=True) as resp:
            # Assertion 1: HTTP status must be 200
            if resp.status_code != 200:
                resp.failure(f"Status code error: {resp.status_code}")
                return

            # Assertion 2: response body must be valid JSON
            try:
                data = resp.json()
            except json.JSONDecodeError:
                resp.failure("Response is not valid JSON")
                return

            # Assertion 3: business code must be 0
            if data.get("code") != 0:
                resp.failure(f"Business error: code={data.get('code')}, msg={data.get('msg')}")

            # Assertion 4: required field exists
            if "data" not in data or not data["data"]:
                resp.failure("data field missing or empty")

The with ... as resp syntax is Python's context manager. Entering the with block pauses Locust stats; exiting marks success/failure based on whether you called resp.failure().

The success() method

You can explicitly call resp.success() to mark success, but it is not required — as long as you didn't call failure(), Locust auto-marks success on exiting the block.

6.3 Custom Stat Names (name= Parameter) ​

When URLs contain dynamic path params (e.g., /api/users/12345), each different ID generates a separate stat row, causing table explosion. Use name= to aggregate them:

python
import random
from locust import HttpUser, task, between

class SearchUser(HttpUser):
    wait_time = between(1, 3)
    host = "https://api.example.com"

    @task
    def search_products(self):
        product_id = random.randint(1, 10000)

        # Without name= → 10,000 different URL rows in stats
        # With name= → all requests aggregated into one "/api/products/[id]" row
        with self.client.get(
            f"/api/products/{product_id}",
            name="/api/products/[id]",
            catch_response=True
        ) as resp:
            if resp.status_code == 200:
                data = resp.json()
                if data.get("price") is None:
                    resp.failure("Price field missing")
            else:
                resp.failure(f"HTTP {resp.status_code}")

6.4 Chapter Summary ​

  • catch_response=True pauses auto-marking for custom business validation;
  • resp.failure(msg) marks a failure with a custom message; resp.success() is optional;
  • name= aggregates dynamic URL paths into one stat row — essential for meaningful percentiles;
  • Always validate both HTTP status AND response body for production-grade tests.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does catch_response=True do?

2. If you do not call resp.failure() inside a catch_response block, what happens on exit?

3. Why is name= critical when testing endpoints with dynamic IDs?

4. What is the difference between HTTP status validation and body validation?

🛠️ Hands-On Exercises ​

  1. Write a test that validates both HTTP status AND response body for a JSON API; mark failure on missing fields.
  2. Add name="/api/users/[id]" to a loop over 100 user IDs; verify the stats table has one row instead of 100.
  3. Combine catch_response=True with name= in a single task; assert status code, JSON validity, and business code in one block.

Proceed to Chapter 7: TaskSet and Nested Tasks.