v13.39.0. For basic usage, see HTTP Client. For preparing a test environment, see Package testing.
Share the Factory, create a PendingRequest every time
Laravel’s HTTP client consists of objects with different responsibilities.FoundationServiceProvider registers the Factory as a singleton, and the Http facade resolves the same class name from the container. As a result, in a normal Laravel application, requests made through a Factory received from the container can also be verified with Http::fake(). Using the facade directly is a valid design, but this page injects the Factory to make the dependency explicit.
Regular request configuration methods called on the Factory, such as baseUrl(), are forwarded through __call() to a new PendingRequest. createPendingRequest() passes along the fake callbacks and stray request restrictions in effect at creation time, and newPendingRequest() applies the shared middleware and shared options.
Group API operations into a small client
As an example, consider anAcme\Catalog package that fetches products from a fictional catalog API. src/CatalogClient.php holds only the Factory and the base URL, and builds the request while product() is running.
product(42) after product(42, 'ja') on the same client does not keep the Accept-Language header added by the previous call. Shared configuration that the consuming application set on the Factory continues to apply to every request.
- Apply package-specific configuration to the newly created PendingRequest. Avoid setting
Http::globalOptions()during package initialization, because it affects unrelated requests as well. - Do not send requests in the constructor. Design the package so that resolving the service, starting Artisan, or publishing configuration files never connects to the external API.
throw()turns 4xx and 5xx responses into aRequestException. Keep it distinct from theConnectionExceptionraised for connection failures and timeouts, and do not replace failures with success values such as an empty array.json(flags: JSON_THROW_ON_ERROR)turns invalid JSON into aJsonException. The example only checks whether the result is an array, so with a real API you should also validate required fields and data types.
Register it in a service provider
Provide default settings in the package’sconfig/catalog.php. Do not store HTTP clients or closures there; only put values that can be stored in the configuration cache.
src/CatalogServiceProvider.php merges the configuration and passes the shared Factory when the client is resolved. It does not override the binding for the Factory itself.
config/acme-catalog.php to the real API. The catalog.example.test host in the example is fictional and used only for explanation and fakes.
bind(), but if the object holding the client is a singleton, that reference is not recreated every time. What matters is not storing call-specific languages, credentials, or request objects in properties. For details, see Designing package service lifetimes.
Overriding configuration and refreshing the cache at deploy time are covered in Merging and caching package configuration. Manage the base URL as trusted application configuration, and do not let request input freely choose the destination.
Verify the real client with fakes
If you mock the client itself, you cannot confirm whether it uses the shared Factory or builds URLs and headers correctly. In package tests, resolve the realCatalogClient from the container and fake only the HTTP responses.
The following example runs in an Orchestra Testbench test environment. Enable preventStrayRequests() in each test’s setUp(). In tests that make requests, configure responses only for the expected URLs and do not add a '*' fallback, so that incorrect URLs are also detected.
assertSent() verifies that at least one matching request was sent. Combine it with assertSentCount() so that unintended extra requests are not missed. assertSentInOrder() verifies both count and order, so it also works for checking state isolation when the same client is used repeatedly.
The guard is configured after parent::setUp(). This test alone cannot verify requests made while providers boot, so keep the design that registration and booting never make requests.
Fakes confirm the requests built by the client and how it handles the responses you prepared. They do not guarantee authentication with the real service, TLS connections, actual timeout durations, or changes to the external API’s specification. Run verification involving external connections separately, in an explicitly prepared test environment.
Additional checks for long-term maintenance
Separating the responsibilities of the shared Factory and each PendingRequest lets consuming applications use Laravel’s normal testing approach as is. Rather than building your own fake mechanism first, start with an implementation that goes through the shared Factory and tests that confirm it.
Primary sources
- Laravel documentation: HTTP Client
- Laravel documentation: Package Development
- Framework v13.39.0: Factory fake, createPendingRequest, and __call
- Framework v13.39.0: PendingRequest configuration and stub handling
- Framework v13.39.0: Response throw and JSON decoding
- Framework v13.39.0: Http facade resolution target and fake
- Framework v13.39.0: FoundationServiceProvider singleton registration
- Laravel framework tests: HttpClientTest