Testing with Pest/PHPUnit in Laravel
Previously we created all these tests using PHPUnit, but in Laravel we have another framework for performing the tests available, Pest; in practical terms, they have more similarities than differences and as we translate the tests previously performed with PHPUnit, we will see a fundamental difference, a simpler, cleaner and more expressive syntax and, in general, more modern.
It is important to mention that in a Laravel project, we can use PHPUnit and/or Pest without making any changes, the key file for PHPUnit is:
tests\TestCase.php
And for Pest:
tests\Pest.php
It is also important to remember that when creating a project in Laravel, it asks which framework you want to use, and it is the one used by default when executing the command:
$ laravel new <ProyectName>
We can also specify via an option if we want to create the test for PHPUnit:
$ php artisan make:test <Class>Test --unit
Or Pest:
$ php artisan make:test <Class>Test --pest
With Pest, we don't use classes, we have methods like the following:
test('performs sums', function () {
$result = sum(1, 2);
expect($result)->toBe(3);
});
As an alternative to the test() function, Pest provides the convenient it() function that simply prefixes the test description with the word "it", making your tests more readable:
it('performs sums', function () {
$result = sum(1, 2);
expect($result)->toBe(3);
});
Finally, we can group related tests:
describe('sum', function () {
it('may sum integers', function () {
$result = sum(1, 2);
expect($result)->toBe(3);
});
it('may sum floats', function () {
$result = sum(1.5, 2.5);
expect($result)->toBe(4.0);
});
});
}
More information in:
https://pestphp.com/docs/writing-tests
In all cases, in the previous functions, we define a text representative of the action to be tested by means of the test, for example:
performs sums
Unit Testing with Pest
The tests with Pest do not change much from those implemented with PHPUnit, it is a slightly simpler syntax and below are the tests previously translated with Pest:
tests/Feature/Api/CategoryTest.php
<?php
use App\Models\Category;
test('test all', function () {
Category::factory(10);
$categories = Category::get()->toArray();
$this->get(
'/api/category/all',
[
'Authorization' => 'Bearer ' . generateTokenAuth()
]
)->assertOk()->assertJson($categories);
});
it("test get by id", function () {
Category::factory(1)->create();
$category = Category::first();
$this->get('/api/category/' . $category->id, [
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(200)->assertJson([
'id' => $category->id,
'title' => $category->title,
'slug' => $category->slug
]);
});
it("test get by slug", function () {
Category::factory(1)->create();
$category = Category::first();
$this->get('/api/category/slug/' . $category->slug, [
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(200)->assertJson([
'id' => $category->id,
'title' => $category->title,
'slug' => $category->slug
]);
});
it("test post", function () {
$data = ['title' => 'Cate 1', 'slug' => 'cate-2-new'];
$this->post('/api/category', $data, [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(200)->assertJson($data);
});
it("test post error form title", function () {
$data = ['title' => '', 'slug' => 'cate-2-new'];
$response = $this->post('/api/category', $data, [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(422);
// $this->assertStringContainsString("The title field is required.", $response->getContent());
$this->assertMatchesRegularExpression("/The title field is required./", $response->getContent());
// $testArray = array("a"=>"value a", "b"=>"value b");
// $value = "value b";
// // assert function to test whether 'value' is a value of array
// $this->assertContains($value, $testArray) ;
// $this->assertContains("The title field is required.",['title'=>'["The title field is required."]']);
});
it("test post error form slug", function () {
$data = ['title' => 'cate 3', 'slug' => ''];
$response = $this->post('/api/category', $data, [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(422);
// $response->assertStringContainsString("The slug field is required.", $response->getContent());
$this->assertMatchesRegularExpression("/The slug field is required./", $response->getContent());
});
it("test post error form slug unique", function () {
Category::create(
[
'title' => 'category title',
'slug' => 'cate-3'
]
);
$data = ['title' => 'cate 3', 'slug' => 'cate-3'];
$response = $this->post('/api/category', $data, [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(422);
$this->assertStringContainsString("The slug has already been taken.", $response->getContent());
});
it("test get by id 404", function () {
$this->get('/api/category/1000', [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(404)->assertContent('"Not found"');
});
it("test get by slug 404", function () {
$this->get('/api/category/slug/cate-not-exist', [
'Accept' => 'application/json',
])->assertStatus(404)->assertContent('"Not found"');
});
it("test put", function () {
Category::factory(1)->create();
$categoryOld = Category::first();
$dataEdit = ['title' => 'Cate new 1', 'slug' => 'cate-1-new'];
$this->put('/api/category/' . $categoryOld->id, $dataEdit, [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(200)->assertJson($dataEdit);
});
it("test delete auth", function () {
Category::factory(1)->create();
$category = Category::first();
$this->delete('/api/category/' . $category->id,[], [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])->assertStatus(200)
->assertContent('"ok"');
$category = Category::find($category->id);
$this->assertEquals($category, null);
});
Since these are resources protected by authentication, we define the method to generate the token within the Pest class:
tests/Pest.php
uses(TestCase::class, RefreshDatabase::class)->in('Feature');
function generateTokenAuth()
{
User::factory()->create();
return User::first()->createToken('myapptoken')->plainTextToken;
}
In the above file, you can also see that they have the RefreshDatabase trait that we used earlier with PHPUnit.
Video Transcript
Congratulations if you got to this class, which according to what I have here, at least what I recorded initially. I don't know if I later recorded any intermediate class. But at least for what I recorded the first time, this would be class 63, not counting the introduction. If we count the introduction, it would be class 64. So, well, if you got to this point, well, a huge congratulations. And if, well, if you saw the first one and suddenly you saw pes and you came here, and it would be the second class that you're watching, likewise, congratulations.
PHP Unit, el framework para testing moderno
What it is basically is the same as php unit but in this case with another technology is as if I told you View and rea, that is, they are two types of framework that we have to perform unit tests, each one has its advantages and disadvantages and there you can, well, look for the interpretation of many people, I see it a little, I'll explain it to you as I see it, php Unit would be like the oldest approach, the original yp would be a slightly more modern approach and we'll see this a little more when we see its syntax a little, that is, you can see that it is a syntax that is a little more modern, cleaner, prettier, more organized, that is, we remove the classes and the rest, that is, it is a test, for the rest, as far as the assertion methods are concerned, which here is the star of the tests, it is more or less the same, we are going to do it. But we have at least many that are similar. Well, here I did not leave any or if not, here I have one, here you can see the assertion method that we also have, the acer json but in this case Remember that we are using Pest and not the php unit one, in the end it is more or less the same, we are going to see also how to generate the token since that is to say where we can place this type of methods that we are going to use in multiple places of the tests then we are going to see that little by little then well surely what is worrying you is that and I am going to answer you in one Yes we are going to do the same tests that we did before but in this case with pes Then there would be another 60 classes more well we are not going to handle it that way since we are going to do the first ones and then from there I simply implement them and I will show you and explain the code a little since well I already see a little exaggerated to do another 60 classes at least to do everything with pes if it already has a very similar structure and I simply do it more than anything for well so that you see another possible implementation using another framework and well a little this because again it is one of the possible candidates that we have when we create a project in laravel Remember that when we create a project in laravel it asks us force If we want to use php unit or if we want to use pes and well in this case again because it is the most traditional I used php unit initially that is to say because it is like the reference and Right now, well, as much as it may be, I consider it important to use pest, which is the most modern form, so that you have both points of view and when you want to implement a project and you decide which one you want to use or depending on the needs of the person who requests the project.
Laravel, support for Pest and PHPUnit
So at least you know how to start the good news is that we can use without any problem both pes and php unit in the same project again without any problem here the only thing that I have seen is the difference if we select one or the other When we create the project is that when we create the test it is automatically generated from the test based on the selected technology, that is, if we had placed php unit as I did with this project then the test is automatically created the test is created in php unit if we had selected pest it is created with pest but nothing happens we can also create a test in a technology of this one of these specific frameworks simply here by placing the option of —pest or —unit that their respective frameworks create, that is, one will create the class and the other will create a template As you saw before for the rest it would be exactly the same and well for testing issues we are simply going to comment on each of the frameworks or the projects that we work on, we are going to comment on the previous tests or we remove them from the test folder to make the tests cleaner and see exactly what is happening and how it works so it is a little bit that's what I wanted to tell you and Well initially if we are going to create the first tests or the first Test with pes and then from there we are going to do it again quickly I am going to make it finish implementing the class or the file separately and then from there I will explain to you what I did or at least give you a quick introduction to it so as not to lengthen this too much since in the end we already completed this in which we already did or tested the complete process of cabar rabo resapi and also here the part of the dashboard we tested each one of those modules And well we no longer have anything else to test since again we are not going to test modules that are or that are part of the framework so nothing That was what I wanted to tell you Ah well also important that was what I mentioned to you a little bit here just to see if I find it like with php unit.
Pest.php and TestCase.php
We have a file called test Case to be able to make the connections, that is, there we have the Laravel code in pest. We also have a class called pest. You can see it here. If you don't have it, it's okay. You can create another project and copy it here. Or you can also go to my repository and from the project and copy this class so you can have it. And if not, when you make a request like get, post delete, and the whole thing, it will give you an error and it will tell you that it is not recognized. Otherwise, I'll give you a heads up here, too. I think I left it here. When placing Although it is I did Sorry in the one of the Rest API which is the first one that we are going to work on here perfectly again you can place that I deleted it Well then I put it again here you can place method to generate the authentication token which was the one we used initially and it is from the test which is the only one implemented here but we will see that From the following class another important thing about Test before finishing is a bit of its syntax here we can use a function called Test in which here we place that logically represents the name of the function, that is to say here we directly place the name of the function, we remove the ones that come down because they would not be necessary and it is what we could place here apart from Test we can also use another method called it() that works In the same way and good to be able to group the tests We also have one called describe() and here we place the tests that are related. For example, if we wanted to create a single file for the dashboard we could do so and create there it would be one for each module and in each of those, we place the tests for each of these, well for these modules that could be or we directly create a separate file as you want but well at least we have that advantage to organize our tests a little if they don't end up there so scattered. Well that's the main thing really, as I told you in the end they are again another framework very similar to the one we saw previously, it has more relationships than differences of course so nothing more to say now yes we go to the next class in which we are going to begin to create our tests with pest.
- Andrés Cruz
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter