NAME

Test::HTTP::Scenario - Deterministic record/replay of HTTP interactions for test suites

VERSION

Version 0.01

SYNOPSIS

use Test::Most;
use Test::HTTP::Scenario qw(with_http_scenario);

with_http_scenario(
    name    => 'get_user_basic',
    file    => 't/fixtures/get_user_basic.yaml',
    mode    => 'replay',
    adapter => 'LWP',
    sub {
        my $user = $client->get_user(42);
        cmp_deeply($user, superhashof({ id => 42 }));
    },
);

DESCRIPTION

Test::HTTP::Scenario lets you test HTTP-based code without ever hitting the real network, by recording real interactions once and replaying them forever. It provides a deterministic record/replay mechanism for HTTP-based test suites. It allows you to capture real HTTP interactions once (record mode) and replay them later without network access (replay mode). This makes API client tests fast, hermetic, and fully deterministic.

Adapters provide the glue to specific HTTP client libraries such as LWP. Serializers control how fixtures are stored on disk.

MODES

record

Real HTTP requests are executed. Each request/response pair is normalized and appended to the fixture. The fixture file is written at the end of run().

replay

No real HTTP requests are made. Requests are matched against the fixture in order, and responses are reconstructed from stored data.

STRICT MODE

If strict => 1 is enabled, replay mode requires that all recorded interactions are consumed. If the callback returns early, run() croaks with a strict-mode error.

DIFFING

If diffing => 1 (default), mismatched requests produce a detailed diff showing expected and actual method, URI, and normalized request structures.

ADAPTERS

Adapters implement:

  • request/response normalization

  • response reconstruction

  • temporary monkey-patching of the HTTP client library

Available adapters:

  • LWP

You may also supply a custom adapter object.

SERIALIZERS

Serializers implement encoding and decoding of fixture files.

Available serializers:

  • YAML (default)

  • JSON

USING RECORD AND REPLAY IN REAL-WORLD APPLICATIONS

This section describes the recommended workflow for using Test::HTTP::Scenario in a real-world test suite. The goal is to capture real HTTP traffic once (record mode) and then replay it deterministically in all subsequent test runs (replay mode).

Overview

Record/replay is designed for API client libraries that normally make live HTTP requests. In record mode, the module performs real network calls and stores normalized request/response pairs in a fixture file. In replay mode, the module prevents all network access and returns synthetic responses reconstructed from the fixture.

This allows your test suite to:

  • run without network access

  • avoid flakiness caused by external services

  • run quickly and deterministically in CI

  • capture complex multi-step API flows once and reuse them

Typical Workflow

Step 1: Write your test using with_http_scenario

use Test::Most;
use Test::HTTP::Scenario qw(with_http_scenario);

with_http_scenario(
    name    => 'get_user_flow',
    file    => 't/fixtures/get_user_flow.yaml',
    mode    => $ENV{SCENARIO_MODE} || 'replay',
    adapter => 'LWP',
    sub {
        my $user = MyAPI->new->get_user(42);
        is $user->{id}, 42, 'user id matches';
    },
);

Step 2: Run the test suite in record mode

$ SCENARIO_MODE=record prove -l t/get_user_flow.t

This performs real HTTP requests and writes the fixture file:

t/fixtures/get_user_flow.yaml

Step 3: Commit the fixture file to version control

The fixture becomes part of your test assets. It should be treated like any other test data file.

Step 4: Run the test suite normally (replay mode)

$ prove -l t

Replay mode:

  • loads the fixture

  • intercepts all HTTP requests

  • matches them against the recorded interactions

  • returns synthetic responses

No network access is required.

Updating Fixtures

If the API changes or you need to refresh the recorded data, simply delete the fixture file and re-run the test in record mode:

$ rm t/fixtures/get_user_flow.yaml
$ SCENARIO_MODE=record prove -l t/get_user_flow.t

Example: Multi-Step API Flow

Record mode captures each request in order:

with_http_scenario(
    name    => 'create_and_fetch',
    file    => 't/fixtures/create_and_fetch.yaml',
    mode    => $ENV{SCENARIO_MODE} || 'replay',
    adapter => 'LWP',
    sub {
        my $api = MyAPI->new;

        my $id = $api->create_user({ name => 'Alice' });
        my $user = $api->get_user($id);

        is $user->{name}, 'Alice';
    },
);

Replay mode enforces the same sequence, ensuring your client behaves correctly across multiple calls.

Notes

  • Replay mode never performs real HTTP requests.

  • Strict mode can be enabled to ensure all interactions are consumed.

  • Diffing mode provides detailed diagnostics when a request does not match.

  • Fixtures are stable across platforms and Perl versions.

METHODS

new

Construct a new scenario object.

Purpose

Initializes a scenario with a name, fixture file, mode, adapter, and serializer. Loads adapter and serializer classes and binds the adapter to the scenario.

Arguments

  • name (Str, required)

    Scenario name.

  • file (Str, required)

    Path to the fixture file.

  • mode (Str, required)

    Either record or replay.

  • adapter (Str|Object, required)

    Adapter name such as LWP or an adapter object.

  • serializer (Str, optional)

    Serializer name, default YAML.

  • diffing (Bool, optional)

    Enable or disable diffing, default true.

  • strict (Bool, optional)

    Enable or disable strict behaviour, default false.

Returns

A new Test::HTTP::Scenario object.

Side Effects

Loads adapter and serializer classes dynamically. Binds the adapter to the scenario.

Notes

The adapter object persists across calls to run().

run

Execute a coderef under scenario control.

Purpose

Installs adapter hooks, loads fixtures in replay mode, executes the callback, and saves fixtures in record mode. Ensures uninstall and save always occur.

Arguments

  • CODE (Coderef, required)

    The code to execute while the adapter hooks are active.

Returns

Whatever the coderef returns, preserving list, scalar, or void context.

Side Effects

  • Installs adapter hooks.

  • Loads fixtures in replay mode.

  • Saves fixtures in record mode.

  • Uninstalls adapter hooks at scope exit.

Notes

Exceptions propagate naturally. Strict mode enforces full consumption of recorded interactions.

with_http_scenario

Convenience wrapper for constructing and running a scenario.

Purpose

Creates a scenario object from key/value arguments and immediately executes run() with the supplied coderef.

Arguments

Key/value pairs identical to new, followed by a coderef.

Returns

Whatever the coderef returns.

Side Effects

Constructs a scenario and installs adapter hooks during execution.

Notes

The final argument must be a coderef.

handle_request

Handle a single HTTP request in record or replay mode.

Purpose

In record mode, performs the real HTTP request and stores the normalized request and response. In replay mode, matches the incoming request against stored interactions and returns a synthetic response.

Arguments

  • req (Object)

    Adapter-specific request object.

  • do_real (Coderef)

    Coderef that performs the real HTTP request.

Returns

  • In record mode: the real HTTP::Response.

  • In replay mode: a reconstructed HTTP::Response.

Side Effects

  • Appends interactions in record mode.

  • Advances the internal cursor in replay mode.

Notes

Matching is currently based on method and URI only. Diffing mode produces detailed mismatch diagnostics.

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

BUGS

SEE ALSO

REPOSITORY

https://github.com/nigelhorne/Test-HTTP-Scenario

SUPPORT

This module is provided as-is without any warranty.

Please report any bugs or feature requests to bug-test-http-scenario at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-HTTP-Scenario. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

You can find documentation for this module with the perldoc command.

perldoc Test::HTTP::Scenario

You can also look for information at:

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to licence terms.

The licence terms of this software are as follows:

  • Personal single user, single computer use: GPL2

  • All other users (including Commercial, Charity, Educational, Government) must apply in writing for a licence for use from Nigel Horne at the above e-mail.