Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzler Laravel Package

blastcloud/guzzler

Guzzler is a lightweight, Laravel-friendly testing helper for Guzzle that lets you fake HTTP requests and assert on what was sent. Define expected requests, return canned responses, and verify methods, URLs, headers, and bodies in your PHP tests without hitting real APIs.

Deep Wiki
Context7

Getting Started

Install as a dev dependency; it is a test-only package and has no business being in production composer.json:

composer require --dev blastcloud/guzzler

The entry point is the UsesGuzzler trait. Add it to a test class and everything you need hangs off $this->guzzler and $this->getClient():

use BlastCloud\Guzzler\UsesGuzzler;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

class InvoiceSyncTest extends TestCase
{
    use UsesGuzzler;

    public static function setUpBeforeClass(): void
    {
        static::setUpGuzzler();       // initialises mock history
    }

    public function test_syncs_invoices(): void
    {
        $this->guzzler->expects($this->once())
            ->post('/v1/invoices')
            ->withJson(['customer' => 'cus_123'])
            ->willRespond(new Response(201, [], json_encode(['id' => 'inv_1'])));

        $client = $this->getClient();   // Guzzle client wired to Guzzler

        $service = new InvoiceService($client);
        $service->sync('cus_123');

        $this->guzzler->assertAll();    // every expectation was hit
    }
}

Two things to internalise first: the client must come from $this->getClient(), and you should call $this->guzzler->assertAll() at the end of the test (or in tearDown) to fail when an expectation was never matched. That pair — a mock-backed client and a global assertion — is the whole mental model.

The first use case is almost always: an application service calls a third-party HTTP API (Stripe, OpenWeather, an internal microservice), and you want a fast, deterministic test that asserts the outbound contract without touching the network.

Implementation Patterns

Inject the Guzzler client, don't new Client(). In Laravel, bind it before exercising the code under test:

$this->app->instance(\GuzzleHttp\ClientInterface::class, $this->getClient());
// or bind the concrete Client, or a wrapper service that accepts a client

If your code resolves a wrapper/repository around Guzzle, bind the mock client into that wrapper — it's cleaner than reaching into the container for Client.

Arrange multiple expectations to sequence responses. Each expects() call is matched in order, so repeated calls to the same endpoint return successive responses:

$this->guzzler->expects($this->once())
    ->get('/stock/AAPL')
    ->willRespond(new Response(200, [], json_encode(['px' => 100])));

$this->guzzler->expects($this->once())
    ->get('/stock/AAPL')
    ->willRespond(new Response(500, [], 'upstream down'));

Match on headers, query, and body to pin the outbound contract; these are the assertions you actually want to fail loudly when a payload changes:

$this->guzzler->expects($this->once())
    ->post('/v1/charges')
    ->withHeader('Authorization', 'Bearer sk_test_x')
    ->withJson(['amount' => 5000, 'currency' => 'usd'])
    ->willRespond(new Response(200, [], json_encode(['id' => 'ch_1'])));

Callback responses carry dynamic logic — useful when the returned payload depends on the request (echoing an ID, varying by query string) or when you want to assert on the request at response time:

->willRespond(function (RequestInterface $request) {
    $id = json_decode((string) $request->getBody(), true)['customer'];
    return new Response(200, [], json_encode(['customer' => $id]));
});

Exceptions use willThrow, which is how you test retry/circuit-breaker logic without flaky networking:

->willThrow(new \GuzzleHttp\Exception\ConnectException(...));

Wildcards and regex for URL matching keep expectations resilient to path parameters, while still requiring the method to match:

->get('/users/*/orders')          // any single path segment
->url('#^/users/\d+/orders$#')    // explicit regex

Inspect the call log for the "right thing was sent" assertions — $this->guzzler->getRequests() returns the captured RequestInterface collection, so you can assert on bodies Guzzler didn't match against.

Prefer Guzzler over Laravel's Http::fake() when the code under test talks to Guzzle directly; use Http::fake() when the code uses Laravel's Http facade. Mixing both in one suite is fine but be explicit about which layer you're mocking.

Gotchas and Tips

  • The mock is not global. Constructing new \GuzzleHttp\Client() inside a test bypasses Guzzler entirely and will attempt a real request. Every client used by the code under test must descend from $this->getClient() (or the container binding).
  • assertAll() is not automatic. Without it, an expectation that was never invoked is silently ignored and your test passes green despite a dead API call. Put it in tearDown once rather than remembering it per-test, or call it explicitly at the end of each test.
  • Ordering is significant. Expectations match in registration order; if your application calls endpoints in a different order than you registered them, unmatched requests will surface as MockHandler exceptions from Guzzle. If order genuinely doesn't matter, register expectations in call order anyway — it makes the test read like the workflow.
  • Header comparisons are subset-style. Guzzle always adds User-Agent, Host, Content-Length, etc. Match only the headers you care about rather than asserting the entire header set.
  • Body matching is exact string equality unless you use withJson() (structural) or a wildcard. Guzzle re-encodes JSON, so asserting on a raw "{"a":1}" string is brittle; prefer withJson().
  • Response bodies must be strings. new Response(200, [], ['a' => 1]) will not serialise the way you expect; json_encode() (or a PSR-7 stream) is required. Same trap applies to array headers versus string values.
  • willRespond with a callable is the extension point for fixtures, generators, and assertions on the request. It is where you wire in Faker data or verify signed payloads.
  • Streaming and large bodies are buffered by the mock handler; tests for real streaming behaviour can't be meaningfully exercised here. Cover streaming protocol separately with an integration test.
  • Async/promise-heavy code is the weak spot. Guzzler's mock handler is designed for the synchronous path; if you rely on Guzzle promises or pool concurrency heavily, expect to test that surface against a local HTTP stub rather than Guzzler.
  • State resets between tests. Because the trait wires a fresh Guzzler on setup, $this->guzzler is safe to reuse across methods — but if you override setUp without calling the trait's bootstrap, history and expectations leak between tests.
  • Failures are informative but Guzzle-flavoured. An unmatched request surfaces as Guzzle's MockHandler exception listing recent requests; when you see it, read the log before hunting through application code — the problem is almost always a mismatched method/URL/expectation count, not the code under test.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core