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

More PEST Features: Uses, Helpers, Expectations

Now that we know the Pest, let's recreate the ProductsTest with Pest.


First Test

Let's create a new Pest test file, and let's start with the first test_homepage_contains_empty_table test.

php artisan make:test ProductsPestTest --pest

tests/Feature/ProductsPestTest.php:

test('homepage contains empty table', function () {
$this->actingAs($this->user)
->get('/products')
->assertStatus(200)
->assertSee(__('No products found'));
});

But here, we don't have $this->user, so how do we define that? Before, we had a setUp method where we defined user property. In Pest, we have hooks, in this case a beforeEach() hook which will be run before each test as the name states.

beforeEach(function () {
$this->user = \App\Models\User::factory()->create();
});
 
test('homepage contains empty table', function () {
$this->actingAs($this->user)
->get('/products')
->assertStatus(200)
->assertSee(__('No products found'));
});

Let's run this specific test.

pest failed test

And we have a failed test with an error no such table. The refresh database doesn't come automatically. Before, we added the trait RefreshDatabase in the test class. In the Pest, there is a general config file, tests/Pest.php.

We can define uses for some targets. We added RefreshDatabase to be used in every feature test.

tests/Pest.php:

uses(
Tests\TestCase::class,
Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');
 
// ...

Now, this test is green.

pest products first test

Now, let's re-add the createUser function. In Pest you can define helpers.

tests/Pest.php:

use App\Models\User;
 
// ...
 
function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin
]);
}

We can use this createUser function in the beforeEach hook to create regular and admin users.

tests/Feature/ProductsPestTest.php:

<?php
 
beforeEach(function () {
$this->user = \App\Models\User::factory()->create();
$this->user = createUser();
$this->admin = createUser(isAdmin: true);
});
 
test('homepage contains empty table', function () {
$this->actingAs($this->user)
->get('/products')
->assertStatus(200)
->assertSee(__('No products found'));
});

Second Test

Next, let's transform the test_homepage_contains_non_empty_table test.

tests/Feature/ProductsPestTest.php:

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

In most cases, transforming the PHPUnit test into Pest is copied and pasted with changing only syntax.


Expectation

One of the most used Pest features is expectations. It's a different syntax to something like assertEquals or assert something.

For this example, let's transform test_create_product_successful into Pest.

tests/Feature/ProductsPestTest.php:

// ...
 
test('create product successful', function() {
$product = [
'name' => 'Product 123',
'price' => 1234
];
 
$this->actingAs($this->admin)
->post('/products', $product)
->assertRedirect('products');
 
$this->assertDatabaseHas('products', $product);
 
$lastProduct = Product::latest()->first();
$this->assertEquals($product['name'], $lastProduct->name);
$this->assertEquals($product['price'], $lastProduct->price);
});

And the last two assertions, assertEquals, can be made into expect().

tests/Feature/ProductsPestTest.php:

// ...
 
test('create product successful', function() {
$product = [
'name' => 'Product 123',
'price' => 1234
];
 
$this->actingAs($this->admin)
->post('/products', $product)
->assertRedirect('products');
 
$this->assertDatabaseHas('products', $product);
 
$lastProduct = Product::latest()->first();
$this->assertEquals($product['name'], $lastProduct->name);
$this->assertEquals($product['price'], $lastProduct->price);
expect($lastProduct->name)->toBe($product['name']);
expect($lastProduct->price)->toBe($product['price']);
});

Even in this case, to expect calls can be chained.

tests/Feature/ProductsPestTest.php:

// ...
 
test('create product successful', function() {
$product = [
'name' => 'Product 123',
'price' => 1234
];
 
$this->actingAs($this->admin)
->post('/products', $product)
->assertRedirect('products');
 
$this->assertDatabaseHas('products', $product);
 
$lastProduct = Product::latest()->first();
expect($lastProduct->name)->toBe($product['name']);
expect($lastProduct->price)->toBe($product['price']);
expect($lastProduct->name)->toBe($product['name'])
->and($lastProduct->price)->toBe($product['price']);
});

This syntax is very clear and readable, which Pest is all about. If you use Pest for writing tests, the expectations will likely be the most used feature.

three pest tests converted


Commit for this lesson