Back to Course |
Testing in Laravel 11 For Beginners

assertSee() and False Positives

In this lesson, I will show you the difference between asserting something with text on the page and asserting the data. Because assertSee() may be dangerous and incorrect.


The Problem

First, let's see why assertSee() could be dangerous. For example, if we create some product with the name Product 1 and search for the product's name on the page.

tests/Feature/ProductsTest.php:

// ...
 
test('homepage contains non empty table', function () {
Product::create([
'name' => 'Product 1',
'price' => 123,
]);
 
get('/products')
->assertStatus(200)
->assertDontSee(__('No products found'))
->assertSee('Product 1');
});

But what if the text Product 1 is present somewhere else on that page? So, for this example, let's remove the name from showing in the table and add some text above the table.

resources/views/products/index.blade.php:

// ...
<h2>You will see Product 1 and other great products</h2>
<table class="min-w-full divide-y divide-gray-200 border">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Name</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Price</span>
</th>
</tr>
</thead>
 
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
@forelse($products as $product)
<tr class="bg-white">
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->name }}
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
${{ number_format($product->price, 2) }}
</td>
</tr>
@empty
<tr class="bg-white">
<td colspan="2" class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ __('No products found') }}
</td>
</tr>
@endforelse
</tbody>
</table>
// ...

If we visit the /products page, we will see that the table name is empty, but the Product 1 is still present on the page.

product 1 text above table

Now, let's run the tests.

assertSee dangerous pass

As you can see, the tests are all green, but there is no text in the table. So what is the solution?


The Solution

A better way is to test the data that is passed to the Blade View. Sometimes, you can still use assertSee() and assertDontSee() for simple and obvious texts, but a better way is to use assertViewHas().

tests/Feature/ProductsTest.php:

use Illuminate\Database\Eloquent\Collection;
 
// ...
 
test('homepage contains non empty table', function () {
$product = Product::create([
'name' => 'Product 1',
'price' => 123,
]);
 
get('/products')
->assertStatus(200)
->assertDontSee(__('No products found'))
->assertSee('Product 1')
->assertViewHas('products', function (Collection $collection) use ($product) {
return $collection->contains($product);
});
});

In this example, we assert that the key passed in our View is called products and that there is a value of a specific $product that we just created with Eloquent. This test will be even better than just assertSee() with the name because assertViewHas() works with the entire object. So, it will test the product's name and price, which is more accurate.

Now, let's undo the View file changes and return the product name to the table:

resources/views/products/index.blade.php:

// ...
<h2>You will see Product 1 and other great products</h2>
<table class="min-w-full divide-y divide-gray-200 border">
<thead>
...
</thead>
 
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
@forelse($products as $product)
<tr class="bg-white">
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->name }}
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
${{ number_format($product->price, 2) }}
</td>
</tr>
 
...
@endforelse
</tbody>
</table>
// ...

Now, let's re-launch the tests again.

assertViewHas product

And it is green. Now, we are testing for both things. The text is seen on the page, and the data is passed correctly.


PHPUnit example

The solution for PHPUnit syntax is below.

use App\Models\Product;
use Illuminate\Database\Eloquent\Collection;
 
class ProductsTest extends TestCase
{
// ...
 
public function test_homepage_contains_non_empty_table(): void
{
$product = Product::create([
'name' => 'Product 1',
'price' => 123,
]);
 
$response = $this->get('/products');
 
$response->assertStatus(200);
$response->assertDontSee(__('No products found'));
$response->assertSee('Product 1');
$response->assertViewHas('products', function (Collection $collection) use ($product) {
return $collection->contains($product);
});
}
}

By now, you probably understand the main differences between Pest and PHPUnit. In most cases, PHPUnit structure and syntax is OOP, and Pest is not.