Unit and Integration Testing with PHPUnit and Pest in Laravel

Testing is a crucial part of any application we are going to create, regardless of the technology, it is always advisable to perform automatic tests to test the system when new changes are implemented; in this way we save a lot of time since there is no need to perform many of the tests manually but by executing a simple command.

Testing consists of testing components individually; in the case of the application we have built, they would be each of the API methods, as well as any other dependency on these methods; this way, when these automated tests are run, if the application passes all the tests, it means that no errors were found, but, if it does not pass the tests, it means that changes have to be made at the application level or implemented tests.

Why should we make tests?

Testing helps ensure that your application will function as expected and as the application grows in modules and complexity, new tests can be implemented and current tests adapted.

It is important to mention that the tests are not perfect, that is, even if the application passes all the tests, it does not mean that the application is error-free, but it is a good initial indicator of the quality of the software. Additionally, testable code is generally a sign of good software architecture.

 

Testing must be part of the application development cycle to guarantee its proper functioning by running them constantly.

What to test?

Testing should focus on testing small units of code in isolation or individually.

For example, in a Laravel application or a web application in general:

  • Controllers:
    • View Responses
    • State codes
    • Nominal conditions (GET, POST, etc.) for a view function
  • Forms
  • Individual help functions

In Laravel, we officially have two ways to use testing, using Pest and PHPUnit.

Tests are the basis of testing in Laravel, with which we can test the methods that make up our application in isolation.

Testing with Pest/PHPUnit

PHPUnit is one of the frameworks for testing in PHP that comes already installed by default in Laravel. As it is the one that has been part of Laravel for the longest time, it is the one that we are going to cover first. It is important to clarify that to follow this section, you must have selected PHPunit as a testing environment when creating the project in Laravel.

When creating a project in Laravel, it also automatically creates a tests folder, with this, you can realize how important tests are, that although they are not part of the functional development of an application, they are part of the development cycle life of this and creating them is evidence of good programming practices.

As we have mentioned previously, Laravel starting with version 11, one of the most important changes is its cleaning of folders, removing files and folders to merge them with others or generate others on demand as in the case of api.php, but, you can see that the tests folder is still present as soon as we create the project, giving complete evidence of the importance of creating these tests for all the applications we develop.

By default, the tests folder contains two folders:

  • tests/Feature
  • tests/Unit

Unit tests are those that test a specific module of the application, as its name says, it is a unit, something that we cannot divide, for example, a facade, a model, a helper are candidates for unit tests, since they have isolated code of the application and do not communicate with each other as in the case of controllers or components, these tests are stored in tests/Unit.

While the tests/Feature folder is the one used to perform integration tests, such as the controllers or those components that are not individual as in the previous case, otherwise, where many things occur such as database connections, use helpers, facades or similar, return jsons, views, etc. These tests are known as function tests where we test larger blocks of code that usually resolve a complete HTTP response.

By default, Laravel already comes with some tests and files ready to use, one of the example tests is ExampleTest.php and that brings the hello world to our application.

Regardless of whether you are using Pest or PHPUnit, the logic is the same, what changes is the syntax and to execute our tests we have the command:

$ vendor/bin/phpunit

For PhpUnit, or:

$ vendor/bin/pest

For Pest, or easier:

$ php artisan test

For any of the above.

Additionally, you can create a .env.testing file in the root of your project to manage configurations in the test environment. This file will be used in place of the .env file when running Pest and PHPUnit tests or running Artisan commands with the --env=testing option.

 

 

 

Test folder

 

 

To create a unit test, we have the following command:

$ php artisan make:test <ClassTest>

Where the test will be placed in the tests/Feature folder:

Or if you want to create a test inside the tests/Unit folder, you can use the --unit option when running the make:test command:

$ php artisan make:test <ClassTest> --unit

More information in:

https://laravel.com/docs/master/testing

Understanding the tests

So that the use of tests is understood in a less abstract way, we are going to create a small exercise of mathematical operations before starting to create tests to test modules of our application, such as in the case of controllers, that is, the scheme of request/response.

For these first tests, let's create a math operations file like the following:

app\Utils\MathOperations.php

class MathOperations
{
 public function add($a, $b) {
    return $a + $b;
 }

 public function subtract($a, $b) {
    return $a - $b;
 }

 public function multiply($a, $b) {
    return $a * $b;
 }

 public function divide($a, $b) {
    return $a / $b;

 }
}

Now let's generate our first file for the first unit test using:

$ php artisan make:test MathOperationsTest --unit

This will generate a file in:

tests/Unit/MathOperationsTest.php

In which, we create some functions that allow us to test the previous methods to perform mathematical operations:

tests/Unit/MathOperationsTest.php

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

// use App\Utils\MathOperations;

class MathOperations
{

    public function add($a, $b)
    {
        return $a + $b;
    }


    public function subtract($a, $b)
    {
        return $a - $b;
    }


    public function multiply($a, $b)
    {
        return $a * $b;
    }

    public function divide($a, $b)
    {
        return $a / $b;
    }
}

class MathOperationsTest extends TestCase
{

    // public function test_example(): void
    // {
    //     $this->assertTrue(true);
    // }

 public function testAdd()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->add(2, 3);
        $this->assertEquals(5, $result);
    }
    public function testSubtract()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->subtract(5, 3);
        $this->assertEquals(2, $result);
    }
    public function testSubtraction()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->multiply(5, 3);
        $this->assertEquals(15, $result);
    }
    public function testDivide()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->divide(8, 2);
        $this->assertEquals(4, $result);
    }
}

To facilitate the exercise, we copy the contents of MathOperations into the unit file.

In this example, we have four test methods, one for each method defined in the MathOperations auxiliary class that allows testing the operations of addition, subtraction, multiplication and division respectively and with this we can appreciate the heart of the tests, which is through assert or assertion type methods:

  • assertStatus: Check the status code in the response.
  • assertOk: Checks if the response obtained is of type 200.
  • assertJson: Checks if the response is of type JSON.
  • assertRedirect: Checks if the response is a redirect.
  • assertSee: Verifies based on a supplier string, if it is part of the response.
  • assertDontSee: Checks if the supplied string is not part of the response.
  • assertViewIs: Checks if the view was returned by the route.
  • assertValid: Checks if there are no validation errors in the submitted form.

They are nothing more than conditionals with which we verify that the expected results are obtained. In this example, the assertEquals method is used, but there are several with a similar operation and we will see some of them throughout the chapter.

You can see the complete immense list at:

https://laravel.com/docs/master/http-tests#response-assertions

Don't worry about having to learn them all though, we usually use a few of them.

Finally, to run the unit tests, we use the command:

$ vendor/bin/phpunit

And we should see an output like the following:

Time: 00:00.850, Memory: 42.50 MB
OK (29 tests, 65 assertions)

If we cause an error in the auxiliary class, such as adding the same parameter twice, ignoring the other:

public function add($a, $b)
{
    return $a + $a;
}

And we execute:

$ vendor/bin/phpunit

We will see an output like the following:

/MathOperationsTest.php:47
FAILURES!
Tests: 29, Assertions: 65, Failures: 1.

We will see an output like the following:

public function testAdd()
{
    $mathOperations = new MathOperations();
    $result = $mathOperations->add(2, 2);
    $this->assertEquals(4, $result);
}

The tests would pass:

Time: 00:00.733, Memory: 42.50 MB
OK (29 tests, 65 assertions)

But, clearly we have a problem in the definition of the auxiliary class, therefore, the tests are not infallible, they are only a means to verify that we do not find errors in the application but it does not mean that the application is error-free, with we can have a basic and necessary understanding of how unit tests work, with this example, we can now move on to actually test the modules that make up the application.

HTTP requests

Our application is made up of controllers or similar that are consumed through HTTP requests of different types and this is exactly what we usually have to test; unit tests are made up of two main blocks, the aspiration methods and the http methods, which in PHPUnit, we have one method for each HTTP method, that is, get, post, put, patch, or delete methods; to be able to send HTTP requests; we must inherit from the class:

use Tests\TestCase;

- Andrés Cruz

En español

This material is part of my complete course and book; You can purchase them from the books and/or courses section, Curso y libro Laravel 11 con Tailwind Vue 3, introducción a Jetstream Livewire e Inerta desde cero - 2024.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.