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.
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.
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.
I'm new to learning about testing. I signed up for a course to learn more about it, and even though I've tried both PHPUNIT and PEST, I like PHPUNIT more. PEST is simpler, but I still choose PHPUNIT. Maybe if I had learned about PEST first, I would have liked it more, but right now, PHPUNIT is my favorite for testing.😀😀