Back to Course |
Testing in Laravel 9 For Beginners: PHPUnit, Pest, TDD

Be careful with assertSee: test DATA to avoid false positives

In this lesson, I will show you the difference between asserting something with see and asserting 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.

use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class ProductsTest extends TestCase
{
// ...
 
public function test_homepage_contains_non_empty_table(): void
{
Product::create([
'name' => 'Product 1',
'price' => 123,
]);
 
$response = $this->get('/products');
 
$response->assertStatus(200);
$response->assertDontSee(__('No products found'));
$response->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. Sometimes, you can still use assertSee and assertDontSee, but a better way is to use assertViewHas.

In this example, we assert that the key passed in our view is products and that there is a value. If it's just a value, the second parameter can be just it, but in this case, it's more complicated, so we must use a closure. This test will be even better because we will test the value's name and price here.

To test that, first, when a product is created, we must assign it to a variable. And then, in the callback, check if the collection that is passed from the Controller contains that product.

tests/Feature/ProductsTest.php:

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);
});
}
}

Undo the View file.

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>
// ...

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.


Commit for this lesson