2 min read

Auto Trait Invocation with Laravel + Pest

My app uses the spatie/laravel-permission package to manage roles and permissions, and PestPHP for testing.

It’s normal to set up users, roles, and permissions for each test. I created SetupRoles trait to accomodate this.

tests/Feature/Concerns/SetupRoles.php
trait SetUpRoles
{
    /**
     * @var \Spatie\Permission\Models\Role
     */
    protected $superAdminRole;
 
    // the rest of code
 
    public function setUpRoles(): void
    {
        $this->app
            ->make(\Spatie\Permission\PermissionRegistrar::class)
            ->forgetCachedPermissions();
 
        $this->superAdminRole = Role::findOrCreate(RoleEnum::SUPERADMIN->value, 'web');
 
        // the rest of code
    }
}

Normally, we would do this manually in each test file:

uses(Tests\Feature\Concerns\SetUpRoles::class);
 
beforeEach(function () {
    $this->setupRoles();
});

However, we would prefer to automatically run setUpRoles() for each test, similar to how the RefreshDatabase trait works.

To achieve this, we need to extend setUpTraits in our tests/TestCase like so:

tests/TestCase.php
namespace Tests;
 
use Tests\Feature\Concerns\SetUpRoles;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
 
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
 
    /**
     * Boot the testing helper traits.
     *
     * @return array
     */
    protected function setUpTraits()
    {
        $uses = parent::setUpTraits();
 
        if (isset($uses[SetUpRoles::class])) {
            /** @disregard */
            $this->setUpRoles();
        }
    }
}

Essentially, this scans all used classes from the test file. If SetUpRoles trait is detected, it calls the setUpRoles method.

vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php
protected function setUpTraits()
{
    $uses = array_flip(class_uses_recursive(static::class));
 
    if (isset($uses[RefreshDatabase::class])) {
        $this->refreshDatabase();
    }
 
    // ... rest of the code
}

Above is how RefreshDatabase is called automatically from our test.

Finally, we just need to register our trait inside tests/Pest.php like this:

tests/Pest.php
uses(
    Tests\TestCase::class,
    Illuminate\Foundation\Testing\RefreshDatabase::class,
    Tests\Feature\Concerns\SetUpRoles::class,
)->in('Feature', 'Unit');

Done. Now, you don’t need to reference the traits in your test files anymore.