VOOZH about

URL: https://deepwiki.com/hypervel/components/11.2-http-testing-and-request-mocking

⇱ HTTP Testing and Request Mocking | hypervel/components | DeepWiki


Loading...
Last indexed: 7 March 2026 (96fbab)
Menu

HTTP Testing and Request Mocking

This document explains the HTTP client testing infrastructure in Hypervel, including request faking, response stubbing, request assertions, and sequence-based testing. The testing system allows tests to run without making real HTTP requests to external services.

For general HTTP client usage outside of testing, see page 3.6. For process testing and command mocking, see page 11.3.

Purpose and Architecture

The HTTP testing system intercepts outgoing HTTP requests made through the Http facade or Factory class, allowing tests to specify stubbed responses and make assertions about requests that were sent. All testing capabilities are built into the production HTTP client classes (Factory, PendingRequest, Response) rather than requiring separate test doubles.

Key Components:

ComponentClass/MethodPurpose
Request FakingFactory::fake()Enables request interception and recording
URL StubbingFactory::stubUrl()Registers URL-specific response stubs
Response SequencingResponseSequenceDefines ordered response sequences
Request AssertionsFactory::assertSent(), Factory::assertNotSent()Asserts requests matching criteria were sent
Stray PreventionFactory::preventStrayRequests()Prevents unfaked requests from executing
RecordingFactory::recordRequestResponsePair()Stores request/response pairs

Architecture: Faking and Recording Flow


Sources: src/http-client/src/Factory.php183-224 src/http-client/src/Factory.php301-306 src/http-client/src/PendingRequest.php1034-1078 src/http-client/src/PendingRequest.php1177-1217

Request Faking Fundamentals

Enabling Faking Mode

Calling Http::fake() enables request interception and automatically provides 200 OK responses for all requests:


Implementation Details:

The Factory::fake() method performs three operations:

  1. Calls record() to set $this->recording = true
  2. Clears the $this->recorded array
  3. Registers default stub callback in $this->stubCallbacks collection

When no callback is provided, a default closure returning Factory::response() (200 OK with empty body) is registered.

Sources: src/http-client/src/Factory.php183-224 src/http-client/src/Factory.php291-296 tests/HttpClient/HttpClientTest.php76-83

Stub Callback Registration and Execution

The $stubCallbacks collection (a Collection instance) contains closures that intercept requests. When PendingRequest::sendRequest() is called, the buildStubHandler() method creates Guzzle middleware that iterates through callbacks until one returns a non-null response.

Stub Handler Execution Flow


Key Implementation Points:

  • buildStubHandler() wraps each callback in a try-catch to handle promise conversions
  • Callbacks receive Request object and Guzzle options array
  • PromiseInterface responses trigger TransferStats recording via on_stats callback
  • First non-null response short-circuits remaining callbacks

Sources: src/http-client/src/PendingRequest.php1177-1217 src/http-client/src/PendingRequest.php1034-1078 src/http-client/src/Factory.php183-224

URL-Specific Stubbing

Pattern Matching with stubUrl()

The Factory::stubUrl() method registers responses for specific URL patterns using glob-style matching via Str::is():


Pattern Matching Logic:

The stub callback checks: Str::is(Str::start($url, '*'), $request->url()), which automatically prefixes patterns with * if not present. Patterns are evaluated in the order registered via fake(). The first matching pattern determines the response.

Sources: src/http-client/src/Factory.php239-260 tests/HttpClient/HttpClientTest.php508-528

Response Shorthand Syntax

Factory::stubUrl() accepts multiple shorthand formats for convenience:

FormatExampleImplementationResult
Valid status code['example.com' => 201]is_int($callback) && $callback >= 100 && $callback < 600201 response with empty body
Invalid status code['example.com' => 999]is_int($callback) || is_string($callback) fallback200 response with "999" as body
String body['example.com' => 'Hello']Factory::response($callback)200 response with string body
Array body['example.com' => ['foo' => 'bar']]Factory::response($callback) with JSON encoding200 response with JSON body and Content-Type: application/json
Closure['example.com' => fn($request) => ...]Direct invocationDynamic response based on request
ResponseSequence['example.com' => Http::sequence()->...]Invocation with ($request, $options)Sequential responses

Sources: src/http-client/src/Factory.php246-259 tests/HttpClient/HttpClientTest.php99-144

Closure-Based Stubs

Stubs can be closures receiving Request and options, enabling conditional responses:


Sources: src/http-client/src/Factory.php203-220 tests/HttpClient/HttpClientTest.php460-470

Response Sequences

Building Sequences with ResponseSequence

ResponseSequence objects define ordered responses that are consumed sequentially via array_shift(). Each invocation removes and returns the next response:


Exhaustion Behavior:

By default, $failWhenEmpty = true causes OutOfBoundsException when isEmpty() returns true. Use dontFailWhenEmpty() to set $failWhenEmpty = false and return 200 OK instead.

Sources: src/http-client/src/ResponseSequence.php1-133 tests/HttpClient/HttpClientTest.php859-893 tests/HttpClient/HttpClientTest.php895-908

Sequence Methods

MethodPurpose
push($body, $status, $headers)Add response with body
pushStatus($status, $headers)Add response with only status
pushFile($path, $status, $headers)Add response from file
pushFailedConnection($message)Add connection failure
whenEmpty($response)Set fallback response
dontFailWhenEmpty()Return 200 OK when empty
isEmpty()Check if sequence is depleted

Sources: src/http-client/src/ResponseSequence.php36-111

ResponseSequence Invocation Logic


Sources: src/http-client/src/ResponseSequence.php118-131

Shorthand Sequence Syntax

fakeSequence() combines sequence creation with URL registration:


Sources: src/http-client/src/Factory.php229-234 tests/HttpClient/HttpClientTest.php924-932

Request Recording and Assertions

Recording Mechanism

When Factory::fake() is called, $this->recording is set to true. Subsequently, all requests and responses are stored in the $this->recorded array as [Request, Response] tuples via Factory::recordRequestResponsePair().

Recording Points in Request Lifecycle


Recording Locations:

  1. Stub Handler: src/http-client/src/PendingRequest.php1188-1194 - Records after stub callback returns
  2. Connection Failures: src/http-client/src/PendingRequest.php777 - Records with null response
  3. Conditional Check: Recording only occurs if $this->recording === true

Sources: src/http-client/src/Factory.php301-306 src/http-client/src/PendingRequest.php1188-1194 src/http-client/src/PendingRequest.php777

Assertion Methods

MethodPurpose
assertSent(callable $callback)Assert request matching callback was sent
assertNotSent(callable $callback)Assert no request matching callback was sent
assertNothingSent()Assert no requests were sent
assertSentCount(int $count)Assert exact number of requests sent
assertSentInOrder(array $callbacks)Assert requests sent in specific order
assertSequencesAreEmpty()Assert all response sequences depleted

Sources: src/http-client/src/Factory.php311-382

Request Inspection in Assertions

Assertion callbacks receive a Request object (wrapper around PSR-7 RequestInterface) with inspection methods:


Request Inspection Methods:

MethodImplementationPurpose
url()(string) $this->request->getUri()Get full request URL
method()$this->request->getMethod()Get HTTP method
hasHeader($key, $value)Arr::has() with value comparisonCheck header presence/value
header($key)Arr::get($this->headers(), $key)Get header values array
headers()$this->request->getHeaders()Get all headers
body()(string) $this->request->getBody()Get raw body string
data()parameters() or json() based on Content-TypeGet decoded form/JSON data
query()parse_str($this->request->getUri()->getQuery())Get query parameters
offsetGet($key)$this->data()[$offset] via ArrayAccessArray access to data
isForm()hasHeader('Content-Type', 'application/x-www-form-urlencoded')Check if form data
isJson()hasHeader('Content-Type') && str_contains(..., 'json')Check if JSON data
isMultipart()hasHeader('Content-Type') && str_contains(..., 'multipart')Check if multipart
hasFile($name, $value, $filename)Collection filter on $this->dataCheck file attachment

Sources: src/http-client/src/Request.php1-279 tests/HttpClient/HttpClientTest.php524-547

Order-Based Assertions

assertSentInOrder() verifies requests occurred in a specific sequence:


Sources: src/http-client/src/Factory.php322-339 tests/HttpClient/HttpClientTest.php1353-1372

Response Construction

Static Response Factory

Factory::response() creates promise-wrapped PSR-7 responses for use in stubs:


Array bodies are automatically JSON-encoded with Content-Type: application/json header:

Sources: src/http-client/src/Factory.php141-155

Response Helper Methods

The Response class provides numerous status check methods:

CategoryMethods
Successok(), created(), accepted(), successful()
Redirectredirect(), movedPermanently(), found(), notModified()
Client ErrorclientError(), badRequest(), unauthorized(), forbidden(), notFound(), requestTimeout(), conflict(), unprocessableContent(), tooManyRequests()
Server ErrorserverError()
ContentnoContent()

Sources: src/http-client/src/Response.php1-501 src/http-client/src/Concerns/DeterminesStatusCode.php1-145

Response Data Access

Multiple methods extract data from responses:


Sources: src/http-client/src/Response.php61-152 tests/HttpClient/HttpClientTest.php347-431

Connection Failure Simulation

Creating Connection Exceptions with failedConnection()

Factory::failedConnection() returns a closure that creates rejected promises with ConnectException, simulating network failures:


Implementation:

The returned closure uses Create::rejectionFor() with a GuzzleHttp\Exception\ConnectException. Default error message follows cURL format: "cURL error 6: Could not resolve host: {host} ...".

Custom error messages can be provided:


Sources: src/http-client/src/Factory.php160-170 tests/HttpClient/HttpClientTest.php1481-1500

Connection Exception Flow in PendingRequest::send()


Exception Wrapping:

The catch block at src/http-client/src/PendingRequest.php773-782 wraps GuzzleHttp\Exception\ConnectException in Hypervel\HttpClient\ConnectionException, creates a Request object from $e->getRequest(), records the failed request, dispatches ConnectionFailed event, then re-throws.

Sources: src/http-client/src/PendingRequest.php773-782 src/http-client/src/ConnectionException.php1-10

Preventing Stray Requests

Enforcement with preventStrayRequests()

Factory::preventStrayRequests() sets $this->preventStrayRequests = true, which is then propagated to PendingRequest instances via createPendingRequest(). When enabled, unmocked requests throw RuntimeException:


Enforcement Location:

The check occurs in PendingRequest::buildStubHandler() at src/http-client/src/PendingRequest.php1209-1213 After iterating all stub callbacks, if all returned null (no match) and $this->preventStrayRequests === true, it throws before falling through to real request execution.

Sources: src/http-client/src/Factory.php265-270 src/http-client/src/Factory.php407 src/http-client/src/PendingRequest.php1209-1213 tests/HttpClient/HttpClientTest.php1469-1479

Stray Request Detection in buildStubHandler()


Sources: src/http-client/src/PendingRequest.php1177-1217

Temporarily Allowing Stray Requests

allowStrayRequests() disables enforcement:


Sources: src/http-client/src/Factory.php283-286

Advanced Patterns

Multiple Fakes with Reset

Calling Factory::fake() again clears $this->recorded = [] and resets $this->stubCallbacks (via merge with new collection), allowing test isolation:


Implementation Note:

At src/http-client/src/Factory.php187 $this->recorded = [] clears the array. Stub callbacks are replaced (not appended) when using array syntax with fake(), but closures passed directly are merged into the existing collection.

Sources: src/http-client/src/Factory.php183-224 tests/HttpClient/HttpClientTest.php688-703

Request Data Extraction

The Request class parses various data formats for assertion convenience:


Data extraction handles:

  • Form-encoded data (application/x-www-form-urlencoded)
  • JSON payloads (application/json)
  • Query parameters from URL
  • Multipart form data

Sources: src/http-client/src/Request.php127-163 tests/HttpClient/HttpClientTest.php550-671

Custom Decode Logic

Response::decodeUsing() allows custom response parsing:


Sources: src/http-client/src/Response.php106-124 tests/HttpClient/HttpClientTest.php433-454

File Response Sequences

pushFile() loads response bodies from disk:


Sources: src/http-client/src/ResponseSequence.php57-64 tests/HttpClient/HttpClientTest.php865-882

Integration with Test Infrastructure

Usage Pattern in PHPUnit Tests

The standard test workflow follows these steps:

  1. Call Http::fake() to enable interception (in test method or setUp())
  2. Execute application code that makes HTTP requests
  3. Assert on recorded requests using assertSent(), assertNotSent(), etc.
  4. Optionally verify sequences are empty with assertSequencesAreEmpty()

Test Setup:

The test file at tests/HttpClient/HttpClientTest.php57-74 shows standard setup:

  • setUp() creates new Factory instance
  • tearDown() calls m::close() for Mockery cleanup
  • RequestException::truncate() clears static state

Sources: tests/HttpClient/HttpClientTest.php57-74 tests/HttpClient/HttpClientTest.php76-1938

Recorded Data Inspection

The recorded() method provides direct access to request/response pairs:


Sources: src/http-client/src/Factory.php387-399

Event Integration

HTTP events (RequestSending, ResponseReceived, ConnectionFailed) are still dispatched during faked requests, allowing event listener tests:

Sources: src/http-client/src/Events/RequestSending.php src/http-client/src/Events/ResponseReceived.php src/http-client/src/Events/ConnectionFailed.php

Refresh this wiki

On this page