Monday, June 10, 2024

Leveraging PHPUnit Data Providers with Eloquent Model Factories in Laravel Testing


In Laravel, there are several ways to work with model factories in feature testing. For instance, you can create models in the setUp() function if you intend to use them for multiple tests or directly create them in each test case.

However, what if you want to test cases with various data? This is where PHPUnit data providers with Eloquent models come into play.

Using a data provider in feature testing can be tricky because it runs before Laravel bootstraps through the TestCase class executed during setUp(). The data provider executes at the beginning of the PHPUnit process, resulting in the following error if you attempt to use it:
There was 1 PHPUnit error:

1) Tests\Feature\ExampleTest::test_non_admin_users_cannot_access_admin
The data provider specified for Tests\Feature\ExampleTest::test_non_admin_users_cannot_access_admin is invalid A facade root has not been set.
This occurs because when the data provider code is executed, the Laravel application has not yet been bootstrapped!

Solution Using Closure

If you are a Pest PHP user, the "Bound Datasets" example utilizes closure for data models:
it('can generate the full name of a user', function (User $user) {
    expect($user->full_name)->toBe("{$user->first_name} {$user->last_name}");
})->with([
    fn() => User::factory()->create(['first_name' => 'Nuno', 'last_name' => 'Maduro']),
    fn() => User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Downing']),
    fn() => User::factory()->create(['first_name' => 'Freek', 'last_name' => 'Van Der Herten']),
]);
In PHPUnit, we can use a closure to pass code to the test via a data provider without directly trying to create the data:
namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    #[DataProvider('nonAdminUsers')]
    public function test_non_admin_users_cannot_access_admin($user): void
    {
        $response = $this
            ->actingAs($user())
            ->get('/admin')
            ->assertStatus(403);
    }

    public static function nonAdminUsers(): array
    {
        return [
            [fn(): User => User::factory()->player()->create()],
            [fn(): User => User::factory()->coach()->create()],
            [fn(): User => User::factory()->owner()->create()],
        ];
    }
}
Notice the invocation of $user(), which we pass to actingAs(). If you need to use the model in various places, just assign it to a variable. Now, the factory data is created within the test, as desired!
Learn More To delve deeper into HTTP feature testing in Laravel, consult its official documentation.
This article elucidates how to overcome errors when using PHPUnit data providers with Eloquent model factories in Laravel feature testing. The suggested solution involves using closure to defer the creation of factory data into the test itself. This ensures that the application
is properly bootstrapped before the data provider is executed, allowing for seamless testing with various data scenarios. By implementing these techniques, you can streamline your testing process and ensure robust and reliable feature testing in Laravel applications.

0 comments:

Post a Comment