Testing Laravel Applications with PHPUnit

Getting Started with PHPUnit

Laravel has a built-in testing framework – PHPUnit. 

PHPUnit is the most popular framework for testing PHP code. Although its name suggests it is only used for unit tests, it is capable of much more. 

Generally speaking, it allows you to perform two types of tests: unit and functional (integration). In addition, it can provide information about code quality and overall test coverage of the project.

Unit and Functional Tests

You might be wondering what the difference is between these two types of tests. Here is a quick overview. 

Unit Tests

Unit tests target small, isolated pieces of code. Ideally, a unit test tests a single method or a small piece of functionality without touching the rest of the application (database or server).

Essentially, a unit test serves as a check on the correctness of a function and ensures it does what you expect it to do. They can be quite useful, for example, when refactoring large methods. 

Functional Tests

As the application grows in size, it is important to conduct functional tests to verify that all its parts work and interact correctly. If they do not, it is important to know which parts/connections are broken. 

For that, we can use functional tests. Unlike unit tests, functional tests check much more complex functionality and often involve more serious setup than just calling a method. They often test API requests or database operations. 

A single functional test can cover a large chunk of code, and in some cases even test entire user stories from beginning to end.

How to Write Tests in Laravel with PHPUnit

In Laravel, testing support with PHPUnit is included out of the box, and the necessary configuration file – phpunit.xml – is already set up for your application. 

To start testing, simply write your tests and possibly change some default values in the configuration. 

For a Laravel application, tests are usually placed in the Tests\Unit and Tests\Feature folders (this can be changed via the phpunit.xml file). Each test file is named {Name}Test.php. 

To create a new test, you can use the make:test command.

php artisan make:test NameTest

By default, the new test will be placed in the Tests/Feature directory. To initialize a unit test, you need to add the -unit flag at the end.   

php artisan make:test NameTest -unit

To run tests, you can use the php artisan test command or run phpunit from ./vendor/bin/phpunit.

Let's look at a few test examples to better understand what we can do.

Example Laravel Unit Test

Here is a trivial auto-generated unit test for a Laravel application.

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase

{

    public function test_basic_test()

    {

        $this->assertTrue(true);

    }

}

Inside the test, we can assert the result of any method in our codebase. Assert has many different options – you can review them in the PHPUnit documentation.

Typically, we have some output that the method should return given certain input. We import the method's class, call the method, and compare the expected result with the method's result.

Example Laravel Functional Test

Functional tests have more moving parts, making them somewhat more complex. 

Here is an example test that checks if the server is online: 

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;

use Illuminate\Foundation\Testing\WithoutMiddleware;

use Tests\TestCase;

class ExampleTest extends TestCase

{

    public function test_a_basic_request()

    {

        $response = $this->get('/');

        $response->assertStatus(200);

    }

}

For a more complex example, here is a test that checks whether the application can post messages to a Telegram group (assuming the functionality is written and exists in the class). 

<?php

namespace Tests\Feature;

use App\Classes\Telegram\Telegram;

use App\Http\Controllers\PublicController;

use Illuminate\Http\Request;

use PHPUnit\Framework\TestCase;

class TelegramTest extends TestCase

{

    public function testTelegramWritingToGroup() 

    {

        $telegram = new Telegram();

        $response = json_decode($telegram->sendCurlMessage("sendMessage",$telegram- >makeArray(env('GROUP_ID'), "Unit test successfully completed", []));

        $this->assertTrue($response->ok);

    }