Laravel Testing Tips

Laravel Testing Tips

Everything you need to know to customize your Laravel tests

Laravel tests tips

As cool as Laravel is, it does come with lot of testing additions, to make our life easier, lets visit some useful ones, for your next tests.

The royal battle integration vs feature vs unit

Developers community struggles with different explanations wether its a unit test, integration or feature test name.

So while there might be some difference on papers, in real life, we just want to test our application as much as we possibly can.

So we use the Laravel conventions. Any test that needs to hit our app, likely to be an http call, to our route, database middleware ..etc would be a feature test. and anything else, that wont need a http call or our entire application would be a Unit test.

One of the features of Unit tests are the speed boost. specially if you need hundreds of tests.

Configuration and precedence

Whether we are running a unit or a feature test, our configuration would leave in the same place.

Laravel comes with phpunit.xml file, with configuration, however we can use

.env.testing

we can create .env.testing the precedence would be

  1. .env.testing (lowest precedence)
  2. phpunit.xml
  3. system enviroments

We can use them interchangebly to customize our testing depending on enviroment. lets say one for our local enviroment and another for our Continues integration enviroment.

Examples:

  • We would store our Api keys in the .env.testing, it might contain our keys that we dont want other developers of our team to have access to.
  • We would setup our database in the .xml file as we want the same testing configuration across all systems.

Generate a test

php artisan make:test UserRegistrationTest

This will generate a test under features tests folder, with our Laravel application running for every test, so this will hit the actual application.

This test will extend the abstract TestCase class, as well as CreatesApplication trait.

Laravel comes with 2 files in the testing folders, a trait and a calss

CreatesApplication Trait

<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;

trait CreatesApplication
{
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

        return $app;
    }
}

TestCase Class

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}

Adding assertions to our test suite

We can add more tests and assertions to our Application from the TestCase class.

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

        public function assertCustom($expected, $actual)
    {
        return false;
    }
}

Performing additional changes to our application

In our CreatesApplication Trait we might want to change how our application runs

<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;

trait CreatesApplication
{
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

                // application actions

        return $app;
    }
}

Adding configuration to our test class

Every TestClass we make, would be a certain assertions we make, so sometimes we want to have a setup, add tests or could be something else, for the entire Class, so we dont need to perform the same action over and over. We can use the Setup method.

We should declear the :void for our method. otherwise it wont work.

public function setUp() :void
{
    parent::setUp();
    // perform certain actions for this class
}

We can just run the mgirations instead of deleting a database, like to create sqlite file database, and only run the migrations for our tests, this will save us sometime but its a tradeoff.

Laravel Testing Database

When testing an actual application, we have two traits that we can implement in our application that would do some magic for us. the RefreshDatabase trait.

This will automatically for every method/test on our class, deletes any database, performa a migration, so we test from scratch, the trade off this can take longer as this would happen for every single test.

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{

    use RefreshDatabase;

}

Naming convention

When we create a test class we aim to test an actual action we can do on our web application, To see examples we can see Laravel Breeze or Jetstream tests.

  • LoggingControllerTest
  • RegistrationTest

To make it easier to remember it should be very similar to the files structure of our web application (specially controllers) but we can just give names of actions

but lets give an example of logging to our app.

php artisan make:test LoggingControllerTest
// or 
php artisan make:test LoggingUserTest

This class should store all tests and assertions to login a user logic.

  • a guest can login
  • Authenticated user cannot visit the login page
  • Password is required to login
  • ...etc

The language in each test most often contains “should”, “must” or “cannot” to clearly signify that it is a requirement for the action.

Customize failure message.

Sometimes when we are testing our tests will be generic while we want a specific error message. lets say our test subject should be true, but we return false, PHPunit will throw us the error "failed asserting that true is false"

so instead we can

public function test_example()
{
    $this->assertNotNull(null, 'The custom message we want to see when this test fails');

}

Happy Testing