beyondcode / beyondcode/laravel-query-detector

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

Open
#121 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
2.1k
Forks
144
PR merge metrics
No merged PRs in 30d

Description

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:

```php
$response->setContent($content);
```

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

```php
$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:

```php
$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()`:

```php
$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` `` or `.env.testing`) until a fix lands.

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

Adding to `phpunit.xml`:

```xml

```

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)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.