Skip to content

HTTP Mocking

When testing frontend applications, you often need to mock external HTTP calls. The Bridge plugin provides two complementary approaches:

MethodInterceptsUse Case
Bridge::fake()Laravel backend → External APIsPayment gateways, email services, etc.
Bridge::mockBrowser()Browser JavaScript → External APIsPublic APIs called directly from frontend

Understanding the Difference

Backend Mocking (Bridge::fake())

When your Laravel backend calls external APIs (like Stripe, SendGrid):

Browser → Nuxt → Laravel API → Stripe API

              Bridge::fake() intercepts here

Frontend Mocking (Bridge::mockBrowser())

When your frontend JavaScript calls external APIs directly:

Browser → fetch('https://api.weather.com/...') → Weather API

       Bridge::mockBrowser() intercepts here

Backend HTTP Mocking

Use Bridge::fake() when your Laravel application makes HTTP calls to external services.

The Challenge

In browser tests, your Laravel application runs in a separate PHP process from your test. Laravel's built-in Http::fake() won't work — the fake you register in your test isn't visible to the server process.

Test ProcessHttp::fake()✗ Not sharedLaravel API(separate)✗ No fakeStripe API(external)Real calls!

Solution

Bridge::fake() writes fake configuration to a temp file that a Laravel middleware reads:

Test ProcessBridge::fake()✓ WritesfileLaravel APIMiddleware✓ ReadsStripe API(blocked)Faked response

Setup

1. Register the Middleware

Add the middleware to your Laravel application (testing environment only):

php
use TestFlowLabs\PestPluginBridge\Laravel\BridgeHttpFakeMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        if (app()->environment('testing')) {
            $middleware->prepend(BridgeHttpFakeMiddleware::class);
        }
    })
    ->create();
php
protected $middleware = [
    // Only in testing...
    \TestFlowLabs\PestPluginBridge\Laravel\BridgeHttpFakeMiddleware::class,
    // ... other middleware
];

Environment Check

Always wrap the middleware registration with an environment check. You don't want this middleware running in production.

2. Use in Tests

php
use TestFlowLabs\PestPluginBridge\Bridge;

test('checkout process with successful payment', function () {
    Bridge::fake([
        'https://api.stripe.com/*' => [
            'status' => 200,
            'body' => [
                'id' => 'ch_test_123',
                'status' => 'succeeded',
                'amount' => 2999,
            ],
        ],
    ]);

    $this->bridge('/checkout')
        ->fill('[data-testid="card-number"]', '4242424242424242')
        ->click('[data-testid="pay-button"]')
        ->assertSee('Payment Successful');
});

Configuration Options

Each fake accepts these options:

OptionTypeDefaultDescription
statusint200HTTP status code
bodyarray[]Response body (JSON)
headersarray[]Response headers
php
Bridge::fake([
    'https://api.stripe.com/v1/charges' => [
        'status' => 201,
        'body' => ['id' => 'ch_123', 'amount' => 1000],
        'headers' => ['X-Request-Id' => 'req_abc123'],
    ],
]);

API Reference

php
// Register fakes
Bridge::fake([...]);

// Check if fakes are registered
Bridge::hasFakes();

// Get current fake configuration
Bridge::getFakes();

// Clear all fakes (automatic on test end)
Bridge::clearFakes();

Frontend/Browser HTTP Mocking

Use Bridge::mockBrowser() when your frontend JavaScript makes direct HTTP calls to external APIs.

When to Use

  • Weather APIs - Frontend fetches weather data directly
  • Public APIs - Quote of the day, currency rates, etc.
  • Third-party widgets - Analytics, chat widgets, etc.
  • CDN resources - When you need to mock CDN responses

How It Works

Bridge::mockBrowser() injects a JavaScript interceptor into the browser that patches fetch() and XMLHttpRequest before your page JavaScript runs:

┌─────────────────────────────────────────────────────────────────┐
│  Browser Context                                                │
├─────────────────────────────────────────────────────────────────┤
│  1. Bridge injects mock interceptor script                      │
│  2. Script patches window.fetch and XMLHttpRequest              │
│  3. Page loads and JavaScript runs                              │
│  4. fetch('https://api.weather.com/...') is called              │
│  5. Interceptor checks URL against registered patterns          │
│  6. If match found → Return mock response                       │
│  7. If no match → Original fetch proceeds                       │
└─────────────────────────────────────────────────────────────────┘

Basic Usage

php
use TestFlowLabs\PestPluginBridge\Bridge;

test('displays weather from external API', function () {
    Bridge::mockBrowser([
        'https://api.weather.com/*' => [
            'status' => 200,
            'body' => [
                'city' => 'Istanbul',
                'temperature' => 25,
                'condition' => 'Sunny',
            ],
        ],
    ]);

    $this->bridge('/weather')
        ->waitForEvent('networkidle')
        ->assertSee('Istanbul')
        ->assertSee('25°')
        ->assertSee('Sunny');
});

Automatic Application

When browser mocks are registered, bridge() automatically uses the mocking context. You don't need a separate method.

Configuration Options

Same as backend mocking:

OptionTypeDefaultDescription
statusint200HTTP status code
bodymixed{}Response body (will be JSON stringified)
headersarray[]Response headers

URL Pattern Matching

Patterns support wildcards (*) for flexible matching:

php
Bridge::mockBrowser([
    // Match all requests to this domain
    'https://api.weather.com/*' => [...],

    // Match specific endpoint
    'https://api.quotable.io/random' => [...],

    // Match with path wildcards
    'https://api.example.com/users/*/profile' => [...],
]);

Error Handling

Test how your frontend handles API failures:

php
test('shows error when weather API fails', function () {
    Bridge::mockBrowser([
        'https://api.weather.com/*' => [
            'status' => 503,
            'body' => ['error' => 'Service unavailable'],
        ],
    ]);

    $this->bridge('/weather')
        ->waitForEvent('networkidle')
        ->assertSee('Unable to load weather data');
});

test('handles network timeout gracefully', function () {
    Bridge::mockBrowser([
        'https://api.weather.com/*' => [
            'status' => 408,
            'body' => ['error' => 'Request timeout'],
        ],
    ]);

    $this->bridge('/weather')
        ->waitForEvent('networkidle')
        ->assertSee('Please try again');
});

API Reference

php
// Register browser mocks
Bridge::mockBrowser([...]);

// Check if browser mocks are registered
Bridge::hasBrowserMocks();

// Clear all browser mocks (automatic on test end)
Bridge::clearBrowserMocks();

Best Practices

1. Use waitForEvent('networkidle')

When testing pages that make async API calls, wait for network activity to complete:

php
$this->bridge('/dashboard')
    ->waitForEvent('networkidle')  // Wait for API calls to complete
    ->assertSee('Data loaded');

2. Clear Mocks Between Tests

Use beforeEach to ensure clean state:

php
beforeEach(function () {
    Bridge::clearBrowserMocks();
});

3. Match Specific Patterns

Be as specific as possible with URL patterns to avoid unintended matches:

php
// Too broad - might catch unrelated requests
Bridge::mockBrowser([
    'https://*' => [...],  // Don't do this!
]);

// Better - specific domain and path
Bridge::mockBrowser([
    'https://api.weather.com/v1/forecast' => [...],
]);

Combined Usage

You can use both Bridge::fake() and Bridge::mockBrowser() in the same test when your application makes both backend and frontend API calls:

php
test('dashboard loads weather and processes payment', function () {
    // Backend: Laravel → Stripe
    Bridge::fake([
        'https://api.stripe.com/*' => [
            'status' => 200,
            'body' => ['balance' => 10000],
        ],
    ]);

    // Frontend: Browser → Weather API
    Bridge::mockBrowser([
        'https://api.weather.com/*' => [
            'status' => 200,
            'body' => ['temperature' => 22],
        ],
    ]);

    $this->bridge('/dashboard')
        ->waitForEvent('networkidle')
        ->assertSee('Balance: $100.00')  // From Stripe (backend)
        ->assertSee('22°C');             // From Weather API (frontend)
});

When to Use Which

ScenarioMethod
Laravel calls Stripe APIBridge::fake()
Laravel calls SendGridBridge::fake()
Vue/React fetches from public APIBridge::mockBrowser()
JavaScript loads data from CDNBridge::mockBrowser()
Laravel proxies an external APIBridge::fake()
Frontend calls your Laravel APINeither (real call)

Common Use Cases

Payment Gateway (Backend)

php
test('handles payment failure gracefully', function () {
    Bridge::fake([
        'https://api.stripe.com/*' => [
            'status' => 402,
            'body' => [
                'error' => [
                    'type' => 'card_error',
                    'message' => 'Your card was declined.',
                ],
            ],
        ],
    ]);

    $this->bridge('/checkout')
        ->fill('[data-testid="card-number"]', '4000000000000002')
        ->click('[data-testid="pay-button"]')
        ->assertSee('Your card was declined');
});

Quote of the Day (Frontend)

php
test('displays inspirational quote', function () {
    Bridge::mockBrowser([
        'https://api.quotable.io/*' => [
            'status' => 200,
            'body' => [
                'content' => 'The only way to do great work is to love what you do.',
                'author' => 'Steve Jobs',
            ],
        ],
    ]);

    $this->bridge('/home')
        ->waitForEvent('networkidle')
        ->assertSee('The only way to do great work')
        ->assertSee('Steve Jobs');
});

Geolocation Service (Backend)

php
test('shows user location from IP', function () {
    Bridge::fake([
        'https://ipapi.co/*' => [
            'status' => 200,
            'body' => [
                'country_name' => 'Turkey',
                'city' => 'Istanbul',
                'latitude' => 41.0082,
                'longitude' => 28.9784,
            ],
        ],
    ]);

    $this->bridge('/location')
        ->assertSee('Istanbul, Turkey');
});

Limitations

Backend Mocking (Bridge::fake())

  1. Response Sequences: All matching requests return the same response
  2. Request Verification: Cannot verify what requests were made
  3. HTTP Client: Only works with Laravel's HTTP client

Frontend Mocking (Bridge::mockBrowser())

  1. Service Workers: Cannot intercept requests in Service Workers
  2. WebSockets: Only HTTP requests (fetch/XHR) are intercepted
  3. Response Sequences: Same response for all matching requests
  4. Binary Data: Best suited for JSON responses

Troubleshooting

Backend mocks not working

  1. Verify middleware is registered in bootstrap/app.php
  2. Check APP_ENV=testing is set
  3. Ensure URL pattern matches exactly

Frontend mocks not working

  1. Use waitForEvent('networkidle') to wait for async calls
  2. Check browser console for JavaScript errors
  3. Verify URL pattern matches the actual fetch URL
  4. Clear mocks between tests with beforeEach

Mocks not being cleared

Both mock types are automatically cleared on test end. If you're seeing stale mocks:

php
beforeEach(function () {
    Bridge::clearFakes();
    Bridge::clearBrowserMocks();
});

Released under the MIT License.