testingphpunitlaravel-8factoryphp-pest

Laravel factories slowing down testing due to nested relationships


For context, I'm working on a RNG-based motorsport simulator, for lack of a better term. Users can create universes (think FIA in real life terms), in a universe they can create series (think F1, F2, F3 etc) and in each series they can create seasons. In each of these, users can create additional models;

In a universe, a user can create teams and drivers.

In a season, a user can create a calendar using circuits they've added outside any universe, entrants (based on teams they've created in the parent universe) and to these entrants they can add drivers (based on drivers created), which I called "lineups".

The deeper I go with testing these relationships, the more models I need to create through factories to be able to test properly, and the longer it takes for a test to run. I've got a pretty simple test to verify a universe owner can add a driver to an entrant belonging to that universe;

test('a universe owner can add drivers to entrants', function () {
    $user = User::factory()->create();
    $season = createSeasonForUser($user);
    $driver = Driver::factory()->for($season->universe)->create();
    $entrant = Entrant::factory()->for($season)->create();

    $this->actingAs($user)
        ->post(route('seasons.lineups.store', [$season]), [
            'driver_id' => $driver->id,
            'entrant_id' => $entrant->id,
            'number' => 2,
        ])
        ->assertRedirect(route('seasons.lineups.index', [$season]));

    $this->assertDatabaseCount('lineups', 1);
    $this->assertCount(1, $entrant->drivers);
    $this->assertCount(1, $season->drivers);
});

I've got two helper functions to quickly create a series and/or season belonging to a specific user;

function createSeriesForUser(User $user): Series
{
    return Series::factory()->for(Universe::factory()->for($user)->create())->create();
}

function createSeasonForUser(User $user): Season
{
    $series = createSeriesForUser($user);

    return Season::factory()->for($series)->create();
}

As you can see, to test one part of the process, I need to create six models through factories (with some of these factories sometimes calling more factories). I ran the tests five times, timing each part of the test (factories, the actual request, and the assertions), and the factories take up 1,9 seconds on average, with the rest of the tests taking up 0,015 seconds, which doesn't seem right to me.

Ideally I'd create all required database entries before each test file is run, but I've understood this is bad practice. Is there another way to speed up the tests? Sadly I can't make the relationships less nested or complicated, since these are simply the requirement of the website I'm building.

Alternatively, is this approach in general even the right way to test my controller's functionality, or can it be done differently?

To not clutter up the question too much, here's a pastebin with all current factories


Solution

  • Turns out Faker's image() is really slow. I replaced the 'avatar' => $this->faker->image(), in my UserFactory with 'avatar' => null,, and my entire test suit now runs in barely over three seconds, or a second if I run them in parallel.