Skip to content

"The response is not a view"; Alert output breaks assertViewIs() / assertViewHas() in PHPUnit tests #121

Description

@kolydart

I am sorry to drop here a description from ai chatbot (claude code), but this bug haunted me for years.
I did not even know it was from this package, until i tried the fix.
I just post the description here in case it is useful for other developers.


Description

When the Alert output is enabled (the default), feature tests that use Laravel's view assertions can fail with:

The response is not a view.

…even though the response is HTTP 200, the session has no errors, and the controller did return a View.

Root cause

BeyondCode\QueryDetector\Outputs\Alert::output() injects the JS alert by calling:

$response->setContent($content);

Illuminate\Http\Response::setContent() unconditionally executes:

$this->original = $content;

at the top of the method. When $content is a string (as it is in Alert::output()), the original View object that Laravel had stored in $response->original is replaced by a raw string. From that point on, TestResponse::assertViewIs() / assertViewHas() / assertViewMissing() report "The response is not a view.", because they check $response->original instanceof View.

This is the same class of bug Livewire had with its auto-injected scripts, which they fixed in v3.4.0 (livewire/livewire PR #7758) by preserving $response->original after setContent.

Reproduction

  1. Laravel app with beyondcode/laravel-query-detector installed, QUERY_DETECTOR_ENABLED=true, and Alert::class listed in config/querydetector.php output.

  2. A route returning view('foo') that triggers ≥ threshold N+1 queries.

  3. Test:

    $response = $this->get('/foo');
    $response->assertStatus(200);
    $response->assertViewIs('foo'); // ❌ "The response is not a view."

The failure happens only when N+1 queries are actually detected (i.e. the Alert output runs). Below the threshold the test passes, which makes it look intermittent.

Symptoms that make this hard to diagnose

  • assertStatus(200) passes.
  • assertSessionHasNoErrors() passes.
  • Nothing is logged to laravel.log if only Alert::class is configured.
  • The error message ("The response is not a view") points at the test, not the package, so users blame their view / Livewire / @include / a recent refactor.

Suggested fixes (in order of preference)

  1. Restore $response->original after setContent inside Alert::output():

    $original = $response->original;
    $response->setContent($content);
    $response->original = $original;
    $response->headers->remove('Content-Length');

    This matches the approach Livewire adopted in v3.4.0.

  2. Skip the Alert output in the testing environment, e.g. early-return when app()->runningUnitTests() (or when app()->environment('testing')). The injected JS alert() is never visible during tests anyway.

  3. Document the caveat in docs/usage.md: recommend setting QUERY_DETECTOR_ENABLED=false for the testing environment (via phpunit.xml <env> or .env.testing) until a fix lands.

Workaround I'm using (until a fix is released)

Adding to phpunit.xml:

<env name="QUERY_DETECTOR_ENABLED" value="false"/>

This deactivates the entire detector during tests and the view assertions work again. The detector still runs normally in local/development.

Environment

  • PHP 8.4
  • Laravel 12
  • Livewire 3 (with PowerGrid)
  • beyondcode/laravel-query-detector (latest)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions