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.
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.
Now, let's run the tests.
As you can see, the tests are all green, but there is no text in the table. So what is 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.
And it is green. Now, we are testing for both things. The text is seen on the page, and the data is passed correctly.
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.