beyondcode / beyondcode/laravel-query-detector
"The response is not a view"; `Alert` output breaks `assertViewIs()` / `assertViewHas()` in PHPUnit tests
- 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
Assessment
This issue has not been assessed yet.