Claude Code for responses: Mock HTTP Requests in Tests — Claude Skills 360 Blog
Blog / AI / Claude Code for responses: Mock HTTP Requests in Tests
AI

Claude Code for responses: Mock HTTP Requests in Tests

Published: December 12, 2027
Read time: 5 min read
By: Claude Skills 360

responses mocks HTTP requests made with the requests library. pip install responses. import responses, import requests. Activate: @responses.activate def test_fn(): responses.add(responses.GET, "https://api.example.com/users", json=[...], status=200). resp = requests.get("https://api.example.com/users"). Add methods: responses.add(responses.POST, url, json={...}, status=201). Body: responses.add(..., body="raw text"). Headers: responses.add(..., headers={"X-Rate-Limit": "100"}). Match URL: exact by default, match_querystring=True to include query params. Callback: def req_callback(request): return (200, {}, json.dumps({"dynamic": True})), responses.add_callback(responses.GET, url, callback=req_callback). Passthrough: responses.add_passthrough("https://real.api.com"). Calls: responses.calls[0].request.body. len(responses.calls). Context: with responses.RequestsMock() as rsps: rsps.add(...). Assert called: assert responses.assert_call_count(url, 1). Multiple: add same URL multiple times — responses consumed in order. Error: responses.add(responses.GET, url, body=ConnectionError("timeout")). Match body: from responses import matchers, responses.add(..., match=[matchers.json_params_matcher({"key":"val"})]). Query match: match=[matchers.query_param_matcher({"page":"1"})]. URL re: responses.add(responses.GET, re.compile(r"https://api\.example\.com/users/\d+"), ...). Reset: responses.reset(). Claude Code generates responses mock layers for API clients, authentication flows, and paginated endpoint tests.

CLAUDE.md for responses

## responses Stack
- Version: responses >= 0.25
- Activate: @responses.activate decorator or responses.RequestsMock() context
- Add: responses.add(responses.GET/POST/PUT/..., url, json=, status=)
- Body: json= for dict (auto Content-Type) | body= for raw string/bytes
- Callback: responses.add_callback(method, url, callback=fn)
- Match: matchers.json_params_matcher | matchers.query_param_matcher
- Inspect: responses.calls[i].request | responses.assert_call_count(url, n)
- Passthrough: responses.add_passthrough(url) for real requests

responses HTTP Mocking Pipeline

# tests/responses_pipeline.py — HTTP mocking with the responses library
from __future__ import annotations
import json
import re
from typing import Any

import pytest
import requests
import responses
from responses import matchers


# ── 0. Code under test ────────────────────────────────────────────────────────
# These are sample API client functions we want to test with mocked HTTP.

BASE_URL = "https://api.example.com/v1"


def get_user(user_id: int) -> dict:
    """Fetch a user by ID from the API."""
    resp = requests.get(f"{BASE_URL}/users/{user_id}")
    resp.raise_for_status()
    return resp.json()


def create_user(name: str, email: str) -> dict:
    """Create a new user via POST."""
    resp = requests.post(f"{BASE_URL}/users", json={"name": name, "email": email})
    resp.raise_for_status()
    return resp.json()


def list_users(page: int = 1, limit: int = 20) -> dict:
    """Fetch paginated user list."""
    resp = requests.get(f"{BASE_URL}/users", params={"page": page, "limit": limit})
    resp.raise_for_status()
    return resp.json()


def update_user(user_id: int, updates: dict) -> dict:
    """Update user fields."""
    resp = requests.patch(f"{BASE_URL}/users/{user_id}", json=updates)
    resp.raise_for_status()
    return resp.json()


def delete_user(user_id: int) -> bool:
    """Delete a user. Returns True on success."""
    resp = requests.delete(f"{BASE_URL}/users/{user_id}")
    return resp.status_code == 204


def get_with_retry(url: str, max_retries: int = 3) -> dict:
    """Fetch with simple retry on 5xx errors."""
    for attempt in range(max_retries):
        resp = requests.get(url)
        if resp.status_code < 500:
            resp.raise_for_status()
            return resp.json()
    resp.raise_for_status()
    return {}


class PaginatedFetcher:
    """Fetches all pages of a paginated API."""

    def __init__(self, base_url: str):
        self.base_url = base_url

    def fetch_all(self, endpoint: str) -> list[dict]:
        items = []
        page  = 1
        while True:
            resp = requests.get(f"{self.base_url}{endpoint}",
                                params={"page": page, "limit": 100})
            resp.raise_for_status()
            data = resp.json()
            items.extend(data.get("items", []))
            if data.get("next_page") is None:
                break
            page = data["next_page"]
        return items


# ── 1. Basic GET/POST tests ───────────────────────────────────────────────────

class TestGetUser:

    @responses.activate
    def test_get_user_success(self):
        responses.add(
            responses.GET,
            f"{BASE_URL}/users/42",
            json={"id": 42, "name": "Alice", "email": "[email protected]"},
            status=200,
        )
        user = get_user(42)
        assert user["id"]    == 42
        assert user["name"]  == "Alice"
        assert user["email"] == "[email protected]"
        assert len(responses.calls) == 1

    @responses.activate
    def test_get_user_not_found(self):
        responses.add(
            responses.GET,
            f"{BASE_URL}/users/999",
            json={"error": "User not found"},
            status=404,
        )
        with pytest.raises(requests.HTTPError) as exc_info:
            get_user(999)
        assert exc_info.value.response.status_code == 404

    @responses.activate
    def test_get_user_server_error(self):
        responses.add(
            responses.GET,
            f"{BASE_URL}/users/1",
            body=ConnectionError("Connection refused"),
        )
        with pytest.raises(ConnectionError):
            get_user(1)

    @responses.activate
    def test_create_user_validates_body(self):
        """Verify the client sends the correct JSON body."""
        responses.add(
            responses.POST,
            f"{BASE_URL}/users",
            json={"id": 1, "name": "Bob", "email": "[email protected]"},
            status=201,
            match=[matchers.json_params_matcher({"name": "Bob", "email": "[email protected]"})],
        )
        result = create_user("Bob", "[email protected]")
        assert result["id"] == 1

    @responses.activate
    def test_create_user_wrong_body_raises(self):
        """responses raises ConnectionError if the body matcher fails."""
        responses.add(
            responses.POST,
            f"{BASE_URL}/users",
            json={"id": 1},
            status=201,
            match=[matchers.json_params_matcher({"name": "Carol"})],
        )
        # Sending wrong body should cause match failure
        with pytest.raises(Exception):
            create_user("NotCarol", "[email protected]")


# ── 2. Query parameter matching ───────────────────────────────────────────────

class TestListUsers:

    @responses.activate
    def test_list_users_first_page(self):
        responses.add(
            responses.GET,
            f"{BASE_URL}/users",
            json={"items": [{"id": 1}], "total": 50, "page": 1},
            status=200,
            match=[matchers.query_param_matcher({"page": "1", "limit": "20"})],
        )
        result = list_users(page=1, limit=20)
        assert result["total"] == 50

    @responses.activate
    def test_list_users_custom_limit(self):
        responses.add(
            responses.GET,
            f"{BASE_URL}/users",
            json={"items": [], "total": 0, "page": 2},
            match=[matchers.query_param_matcher({"page": "2", "limit": "5"})],
        )
        result = list_users(page=2, limit=5)
        assert result["page"] == 2


# ── 3. Dynamic callbacks ──────────────────────────────────────────────────────

class TestCallbacks:

    @responses.activate
    def test_dynamic_response(self):
        """Callback receives the request and returns a dynamic response."""
        def user_callback(request):
            user_id = int(request.url.split("/")[-1])
            body    = json.dumps({"id": user_id, "name": f"User {user_id}"})
            return (200, {"Content-Type": "application/json"}, body)

        responses.add_callback(
            responses.GET,
            re.compile(r"https://api\.example\.com/v1/users/\d+"),
            callback=user_callback,
        )

        user_10 = get_user(10)
        user_20 = get_user(20)
        assert user_10["name"] == "User 10"
        assert user_20["name"] == "User 20"
        assert len(responses.calls) == 2

    @responses.activate
    def test_rate_limit_callback(self):
        """Simulate 429 on first call, 200 on retry."""
        call_count = [0]

        def rate_limit_callback(request):
            call_count[0] += 1
            if call_count[0] == 1:
                return (429, {"Retry-After": "1"}, '{"error": "rate limited"}')
            return (200, {"Content-Type": "application/json"}, '{"ok": true}')

        responses.add_callback(
            responses.GET,
            f"{BASE_URL}/users",
            callback=rate_limit_callback,
        )

        result = get_with_retry(f"{BASE_URL}/users")
        assert result == {"ok": True}
        assert call_count[0] == 2


# ── 4. Pagination tests ───────────────────────────────────────────────────────

class TestPaginatedFetcher:

    @responses.activate
    def test_fetches_all_pages(self):
        """Mock three pages; assert all items are returned."""
        responses.add(
            responses.GET, f"{BASE_URL}/items",
            json={"items": [{"id": 1}, {"id": 2}], "next_page": 2},
            match=[matchers.query_param_matcher({"page": "1", "limit": "100"})],
        )
        responses.add(
            responses.GET, f"{BASE_URL}/items",
            json={"items": [{"id": 3}, {"id": 4}], "next_page": 3},
            match=[matchers.query_param_matcher({"page": "2", "limit": "100"})],
        )
        responses.add(
            responses.GET, f"{BASE_URL}/items",
            json={"items": [{"id": 5}], "next_page": None},
            match=[matchers.query_param_matcher({"page": "3", "limit": "100"})],
        )
        fetcher = PaginatedFetcher(BASE_URL)
        items   = fetcher.fetch_all("/items")
        assert [i["id"] for i in items] == [1, 2, 3, 4, 5]
        assert len(responses.calls) == 3


# ── 5. Context manager approach ───────────────────────────────────────────────

class TestRequestsMockContext:

    def test_update_user(self):
        """Using RequestsMock context instead of decorator."""
        with responses.RequestsMock() as rsps:
            rsps.add(
                responses.PATCH,
                f"{BASE_URL}/users/7",
                json={"id": 7, "name": "Updated"},
                status=200,
                match=[matchers.json_params_matcher({"name": "Updated"})],
            )
            result = update_user(7, {"name": "Updated"})
        assert result["name"] == "Updated"

    def test_delete_user(self):
        with responses.RequestsMock() as rsps:
            rsps.add(responses.DELETE, f"{BASE_URL}/users/5", status=204)
            assert delete_user(5) is True


# ── 6. Asserting call counts ──────────────────────────────────────────────────

class TestCallInspection:

    @responses.activate
    def test_request_headers_sent(self):
        """Verify a specific header was included in the request."""
        responses.add(responses.GET, f"{BASE_URL}/users/1", json={"id": 1})
        session = requests.Session()
        session.headers.update({"Authorization": "Bearer secret-token"})
        session.get(f"{BASE_URL}/users/1")

        sent_auth = responses.calls[0].request.headers.get("Authorization")
        assert sent_auth == "Bearer secret-token"

    @responses.activate
    def test_exactly_one_call(self):
        responses.add(responses.GET, f"{BASE_URL}/users/1", json={"id": 1})
        get_user(1)
        responses.assert_call_count(f"{BASE_URL}/users/1", 1)


# ── Demo ──────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("responses HTTP Mocking Demo")
    print("=" * 50)
    print("\nRun with pytest to execute all HTTP mock tests:")
    print("  pytest tests/responses_pipeline.py -v")
    print("\nKey patterns:")
    print("  @responses.activate              — intercept all requests in the test")
    print("  responses.add(GET, url, json=)   — register mock response")
    print("  match=[json_params_matcher]      — validate request body")
    print("  match=[query_param_matcher]      — validate query string")
    print("  responses.add_callback           — dynamic response function")
    print("  responses.calls[0].request       — inspect what was sent")
    print("  RequestsMock() context           — alternative to decorator")

For the unittest.mock.patch("requests.get") alternative — patching requests.get at the module level returns a Mock that must be configured with .return_value.json.return_value = {...} for every level of the call chain, and any code that calls requests.Session().get() escapes the patch scope, while @responses.activate intercepts all HTTP traffic at the transport layer regardless of how requests is called (Session, session.get, requests.request), and responses.calls[0].request.body lets you assert exactly what JSON the client sent. For the httpretty library alternative — httpretty patches the socket layer which can interfere with pytest networking fixtures and multiprocessing, while responses patches requests at the adapter level (no socket monkey-patching), matchers.json_params_matcher validates the request body without string parsing, and add_callback returns dynamic responses derived from the request URL or body, making stateful pagination tests (three pages with different next_page values) straightforward with sequential responses.add calls. The Claude Skills 360 bundle includes responses skill sets covering @responses.activate and RequestsMock context, responses.add with json/body/headers/status, dynamic callbacks with add_callback, URL regex matching, json_params_matcher and query_param_matcher, pagination sequential mocking, ConnectionError simulation, call inspection and assert_call_count, and passthrough for selective real requests. Start with the free tier to try HTTP mocking code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free