Pest Test Case
Table of Contents
- Technical Requirements
- Set up Laravel
- Install Pest
- Create the to-do Model, Migration and Controller
- Create the to-do factory
- Configure the database
- Write the tests
- Build the TO-DO application
Pest is a new testing PHP Framework developed by Nuno Maduro. While Pest itself is built on top of PHPUnit, the popular and widely adopted PHP testing framework, Pest aims to provide a better experience for writing tests. The philosophy is simple. Keep the TDD experience simple and elegant by providing expressive interfaces.
In this tutorial, we’ll be looking at how to get started using Pest in a Laravel project. Using the test-driven approach, we’ll be building a simple to-do application that allows you to create, edit, update and delete tasks.
Technical Requirements
- PHP version 7.3 or higher. Pest requires PHP 7.3+ to work.
- Laravel 8.
- Composer.
- A basic understanding of PHPUnit.
- A basic understanding of SQLite. We’ll be making use of SQLite because it makes running our tests faster.
Set up Laravel
There are different ways to set up a new Laravel project. You can do so via the Laravel installer or by using Composer. For the sake of this tutorial, we’ll be using Composer.
Run the following command in your terminal:
$ composer create-project --prefer-dist laravel/laravel pest-todo
This will set up a new Laravel project for us in the pest-todo
directory.
Install Pest
Now that we’ve set up a new Laravel project, there are a couple of additional steps we need to carry out to set up Pest with Laravel. Type cd pest-todo
to change into the new pest-todo
directory, and then run the following command:
$ composer require pestphp/pest --dev
Next, we’ll be installing the Pest Plugin for Laravel. To do that, run the following command:
$ composer require --dev pestphp/pest-plugin-laravel
Once the plugin has been installed, run the following command:
$ php artisan pest:install
This will create a Pest.php file in the tests directory. The Pest.php file is autoloaded automatically and serves as an ideal place to recursively bind helper classes and traits to your tests. Laravel comes bundled with some example test files based on PHPUnit. Let’s change those tests to make use of Pest instead. Head over to the _tests/Feature_
directory and take a look at the _ExampleTest.php_
file. Here’s what it currently looks like:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
To migrate this test to the corresponding Pest implementation, replace the content of the file with the following code:
<?php
it('has welcome page')->get('/')->assertStatus(200);
Much cleaner right? We’ve reduced this ExampleTest file from about 20 lines of code to just 2 lines while testing the exact same thing and producing the same result. That is, it visits the root URL at ‘/’ and asserts that an HTTP status code of 200 is returned.
Pest provides two functions for writing tests - test()
and it()
. Both functions accept a test description as the first argument and a closure that contains the test expectations as the second argument. They share the same syntax and behavior and you’re free to use either one you find fitting. I personally prefer using it()
since they make your test cases read like a complete sentence.
Similarly, let’s make the ExampleTest.php
file located in the tests/Unit directory use Pest as well. Replace the contents of the file with the following code:
<?php
test('basic')->assertTrue(true);
Next, use the following command to run the test suite:
$ ./vendor/bin/pest
All tests should be passing as seen in the image below.
Create the to-do Model, Migration and Controller
Our application is going to have a single Model called Todo
. Laravel provides a handy command for generating a Model, Migration, and Controller for an entity all at once. To do that run the following command:
$ php artisan make:model Todo -m -c
A new migration file is created in the _database/migrations_
directory at _[TODAYSDATE]_create_todos_table.php_
. Next, add the following code to the up()
method within the migration file:
$table->string('name');
$table->boolean('completed')->default(false);
Each to-do task will have a name
attribute as well as a boolean completed
attribute, with a default value of false
. Next, edit the _App/Models/Todo.php_
file with the following code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Todo extends Model
{
use HasFactory;
protected $fillable = ['name', 'completed'];
}
Here, we assign the name
and completed
attribute of the model to be mass-assignable.
Create the to-do factory
Laravel model factories provide a convenient way of seeding the database with data. This is very useful when it comes to testing. Run the following command to create a factory class for the to-do model:
$ php artisan make:factory TodoFactory
This will create TodoFactory.php
for us in the _database/factories_
directory. Edit the definition()
method within the file to return an array similar to the one below:
return [
'name' => 'Deploy Twilio Verify to Live',
'completed' => false
];
The definition
method returns the default set of attribute values that should be applied when creating a model using the factory.
Configure the database
We’ll be making use of an in-memory SQLite database for testing. This will make running our tests faster. Laravel already provides support for using a SQLite database for testing. Head over to the phpunit.xml file located at the root of your project’s directory and uncomment the following lines of code:
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
Write the tests
Now that we’ve done all the necessary setup and configuration, we can get started with writing the tests. These tests are required to have a functioning application and will provide the corresponding implementation to make sure all the tests pass.
Run the following Pest command to create a unit test file:
$ php artisan pest:test TodoTest --unit
This will create TodoTest.php
in the tests/Unit
directory. Replace the file’s code with the following code:
<?php
use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(Tests\TestCase::class, RefreshDatabase::class);
it('does not create a to-do without a name field', function () {
$response = $this->postJson('/api/todos', []);
$response->assertStatus(422);
});
it('can create a to-do', function () {
$attributes = Todo::factory()->raw();
$response = $this->postJson('/api/todos', $attributes);
$response->assertStatus(201)->assertJson(['message' => 'Todo has been created']);
$this->assertDatabaseHas('todos', $attributes);
});
it('can fetch a to-do', function () {
$todo = Todo::factory()->create();
$response = $this->getJson("/api/todos/{$todo->id}");
$data = [
'message' => 'Retrieved To-do',
'todo' => [
'id' => $todo->id,
'name' => $todo->name,
'completed' => $todo->completed,
]
];
$response->assertStatus(200)->assertJson($data);
});
it('can update a to-do', function () {
$todo = Todo::factory()->create();
$updatedTodo = ['name' => 'Updated To-do'];
$response = $this->putJson("/api/todos/{$todo->id}", $updatedTodo);
$response->assertStatus(200)->assertJson(['message' => 'To-do has been updated']);
$this->assertDatabaseHas('todos', $updatedTodo);
});
it('can delete a to-do', function () {
$todo = Todo::factory()->create();
$response = $this->deleteJson("/api/todos/{$todo->id}");
$response->assertStatus(200)->assertJson(['message' => 'To-do has been deleted']);
$this->assertCount(0, Todo::all());
});
At the top of the file, the uses()
method binds the TestCase
class and the RefreshDatabase
trait to the current test file. The base TestCase
class is provided by Laravel and provides helper methods for working with the framework while testing. The RefreshDatabase
trait takes care of migrating and resetting the database after each test so that data from a previous test does not interfere with subsequent tests.
Now, let’s go over what each test is doing:
-
it(‘does not create a to-do without a name field’) - Laravel provides several helpers for testing JSON APIs and their responses. Here we make use of the
postJson
helper to make aPOST
request to theapi/todos
endpoint passing in an empty array. Next, theassertStatus()
method on the returned response ensures that an HTTP status code of 422 should be returned. This test ensures that a name field will always be present on the request payload. -
it(‘can create a to-do’) - This test ensures that a to-do is created on making a
POST
request to theapi/todos
endpoint. We assert that an HTTP status code of 201 is returned and that the database actually contains the to-do using theassertDatabase()
method. -
it(‘can fetch a to-do’) - This test checks to see that a particular to-do task can be fetched using the ID. Using the
create()
method on the Todo Factory, a to-do task is created and stored in the database. Similarly, we assert that the status code returned is 200. TheassertJson()
method converts the response to an array and verifies that the given array exists within the JSON response that will be returned by the application. - it(‘can update a to-do’) - This test ensures that a to-do task can be updated and that the updated task can be found in the database.
- it(‘can delete a to-do’) - This test ensures that a to-do task can be deleted and verifies that the total number of tasks contained within the database is zero.
To run the test suite, run the following command:
$ ./vendor/bin/pest --filter TodoTest
The test suite should be failing since we have not implemented any of the features.
Build the TO-DO application
Let’s provide the corresponding implementation of the tests we’ve written so far. Head over to the TodoController.php
file in the app/Http
directory and replace the file’s code with the following code:
<?php
namespace App\Http\Controllers;
use App\Models\Todo;
use Illuminate\Http\Request;
class TodoController extends Controller
{
public function create(Request $request)
{
$request->validate($this->rules());
$todo = Todo::create($request->only(['name']));
$data = [
'message' => 'To-do has been created',
'todo' => $this->mapTodoResponse($todo)
];
return response()->json($data, 201);
}
public function show(Todo $todo)
{
$data = [
'message' => 'Retrieved To-do',
'todo' => $this->mapTodoResponse($todo)
];
return response()->json($data);
}
public function update(Todo $todo, Request $request)
{
$request->validate($this->rules());
$todo->update($request->only(['name']));
$todo->refresh();
$data = [
'message' => 'To-do has been updated',
'todo' => $this->mapTodoResponse($todo)
];
return response()->json($data);
}
public function delete(Todo $todo)
{
$todo->delete();
$data = [
'message' => 'To-do has been deleted'
];
return response()->json($data);
}
protected function rules()
{
return [
'name' => 'required|string|min:4'
];
}
protected function mapTodoResponse($todo)
{
return [
'id' => $todo->id,
'name' => $todo->name,
'completed' => $todo->completed
];
}
}
- The
create()
method creates a new to-do task. - The
show()
method returns a given task based on it’s ID. - The
update()
method updates a to-do task. - The
delete()
method deletes a given to-do task.
Next, add the following routes to the routes/api.php
file:
<?php
Route::get('/todos/{todo}', 'App\Http\Controllers\TodoController@show');
Route::post('/todos', 'App\Http\Controllers\TodoController@create');
Route::put('/todos/{todo}', 'App\Http\Controllers\TodoController@update');
Route::delete('/todos/{todo}', 'App\Http\Controllers\TodoController@delete');
Now that we’ve provided all the corresponding implementations for the tests, we can go back to running our tests and they should all be passing now. Run the test suite again with the following command:
$ ./vendor/bin/pest --filter TodoTest
Comments (0)
What are your thoughts on "Pest Test Case"?
You need to create an account to comment on this post.