Modern Debugging and Testing in Laravel 11.44 & 12.1

Overview

continues to refine the developer experience by bridging the gap between high-level abstractions and low-level visibility. These updates focus on three critical areas: visualising raw database queries during failures, observing real outbound network traffic without mocks, and isolating specific validation errors in automated tests. These features reduce the friction between writing code and verifying its behavior in the real world.

Prerequisites

To follow this guide, you should be comfortable with:

  • PHP
    8.2 or higher.
  • Basic Eloquent query building and the HTTP facade.
  • Writing feature tests using
    PHPUnit
    or
    Pest
    .

Key Libraries & Tools

  • Laravel Framework (v11.44 / v12.1): The core framework receiving these updates.
  • Guzzle HTTP: The underlying client used by Laravel's HTTP facade.
  • Eloquent ORM: Laravel's database abstraction layer.

Code Walkthrough

1. Better SQL Debugging

Previously, toSql() returned statements with ? placeholders. The new getRawSql() method on query exceptions provides a fully-bound string ready for your SQL client.

try {
    User::where('status', 'active')->where('id', 1)->firstOrFail();
} catch (QueryException $e) {
    // Returns: select * from "users" where "status" = 'active' and "id" = 1
    dump($e->getRawSql());
}

2. Recording Real HTTP Traffic

While faking is standard, sometimes you need to let a request hit a live sandbox while still asserting against it. Use HTTP::record() to capture traffic without intercepting it.

Http::record();

Http::get('https://api.github.com/users/taylorotwell');

Http::assertRecorded(function ($request, $response) {
    return $request->url() === 'https://api.github.com/users/taylorotwell' &&
           $response->successful();
});

3. Isolated Validation Assertions

Testing that a specific field failed while ensuring others passed used to require manual array checks. assertOnlyInvalid simplifies this logic.

$response = $this->post('/register', [
    'email' => '[email protected]',
    'password' => 'secret'
    // 'name' is missing
]);

// Checks that 'name' failed AND email/password passed
$response->assertOnlyInvalid(['name']);

Syntax Notes

  • Closure Context: In assertRecorded, the closure provides both the Request and Response objects, allowing for comprehensive integration testing.
  • Strictness: assertOnlyInvalid is strict; if a field you didn't list also fails validation, the test will fail.

Practical Examples

  • Performance Tuning: Use getRawSql() in your local logging to identify poorly indexed queries that Eloquent might hide behind abstractions.
  • Third-Party Webhooks: Use record() when testing integration with external APIs where you need to verify actual payload delivery to a staging environment.

Tips & Gotchas

  • Internal vs External: HTTP::record() only tracks calls made via the Laravel Http facade. If you instantiate GuzzleHttp\Client directly, those requests stay invisible to the recorder.
  • JSON Response Helper: For API-only projects, use assertOnlyJsonValidationErrors to achieve the same isolation for JSON payloads.
3 min read