Table of Contents
Auto-Generating Test Fixtures from Real API Responses with Guzzle Middleware
When integrating with external APIs, you need test fixtures. Manually crafting them is tedious and error-prone. Here’s a better way: use Guzzle middleware to automatically dump real API responses to files.
The Problem
You’re building a service provider for a third-party API. You need:
- Realistic JSON fixtures for unit tests
- Coverage of all API endpoints you use
- Responses that match the actual API structure (not guesswork)
Manually creating these is painful. The API response structure might have dozens of fields, nested objects, edge cases you haven’t seen yet.
The Solution: Temporary Dumper Middleware
Add a Guzzle middleware to your HTTP client that intercepts responses and saves them to disk:
use GuzzleHttp\HandlerStack;
use Illuminate\Support\Facades\Storage;
// In your service provider's register/boot method
$http = $this->app->make(HttpClientFactory::class)->make($logger);
/** @var HandlerStack $handler */
$handler = $http->getConfig('handler');
$handler->push(function (callable $next) {
return function ($request, array $options) use ($next) {
return $next($request, $options)->then(function ($response) use ($request) {
// Build organized directory structure based on endpoint
$path = trim($request->getUri()->getPath(), '/');
$dir = base_path("temp/api-fixtures/{$path}");
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// Filename based on request body (for POST) or 'get' for GET requests
$body = (string) $request->getBody();
$filename = $body ? md5($body) : 'get';
// Decode, pretty-print, and save
$json = json_decode((string) $response->getBody(), true);
file_put_contents(
"{$dir}/{$filename}.json",
json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
// IMPORTANT: Rewind the stream so the original code can still read it
$response->getBody()->rewind();
return $response;
});
};
}, 'fixture_dumper');
How It Works
Guzzle’s middleware system uses a chain-of-responsibility pattern. This middleware:
- Intercepts the response after the HTTP request completes
- Extracts the endpoint path from the request URI
- Creates an organized directory structure mirroring your API (e.g.,
temp/api-fixtures/users/profile/) - Names files by request body hash (or ‘get.json’ for GET requests)
- Saves pretty-printed JSON to disk
- Rewinds the response body so your actual code can still read it
Example Output
After running your integration locally, you’ll have a fixture library like:
temp/api-fixtures/
βββ products/
β βββ get.json
β βββ abc123.json (POST /products with specific body)
βββ orders/
β βββ get.json
β βββ def456.json
βββ users/
βββ profile/
βββ get.json
Each file contains the exact JSON structure returned by the real API.
Using The Fixtures In Tests
Copy the generated fixtures to your test directory:
// tests/Fixtures/ExternalApi/products/get.json
// tests/Fixtures/ExternalApi/orders/get.json
// In your tests:
$mockClient = new MockHandler([
new Response(200, [], file_get_contents(
__DIR__ . '/../Fixtures/ExternalApi/products/get.json'
)),
]);
$this->app->instance(ApiClient::class, new ApiClient(
new Client(['handler' => HandlerStack::create($mockClient)])
));
When To Remove It
This is temporary development tooling, not production code. Remove the middleware once you have your fixtures:
// β Remove before committing
$handler->push(function (callable $next) { ... }, 'fixture_dumper');
Or make it conditional:
if (config('app.debug') && config('api.dump_fixtures')) {
$handler->push($fixtureD umperMiddleware, 'fixture_dumper');
}
Benefits
- Realistic test data – Exact structure from the real API
- Comprehensive coverage – Hit every endpoint once, get perfect fixtures
- No manual JSON crafting – Let the API do the work
- Update fixtures easily – Re-run with middleware enabled when API changes
- Organized by endpoint – Easy to find the fixture you need
The Takeaway
Guzzle middleware isn’t just for adding auth headers or logging. It’s a powerful tool for development workflows. Use it to auto-generate test fixtures from real API responses, then remove it before production. Your tests get realistic data, and you save hours of manual JSON wrangling.
Leave a Reply