Now that we know the Pest, let's recreate the ProductsTest
with Pest.
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'));});
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.
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.