0

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

Alex
  • 778
  • 3
  • 12
  • 27
  • First of all, move those "helpers" to a class where it can only be loaded and used when you have `required-dev` (`composer`), so you prevent someone calling those outside tests... Second, can you show what is each factory creating? I have created way more factories and relationships and I do not end with even `500ms` per test, so I think there is something inside your factories taking a lot of time. Can you time each factory creation? – matiaslauriti Jan 07 '22 at 06:22
  • The helper functions are in the `Pest.php` file, the only way they'll be executed is when someone already has CLI access, in which case I've got bigger issues. I've attached a pastebin with all current factories, however I won't be able to time them until later today. – Alex Jan 07 '22 at 07:45
  • After checking the Factories, I don't see anything that would add up to take 1.9 seconds, but I did see that you are not correctly defining them. You have to use `Model::factory()` instead of `Model::factory()->create()->id`, check the [documentation](https://laravel.com/docs/8.x/database-testing#defining-relationships-within-factories), maybe that is the issue. Also check the second code after the one I shared with you, you will see how to manipulate content (you have done `$model = Model::factory()->create();` and then used that everywhere, you have to use the second code on the document. – matiaslauriti Jan 07 '22 at 19:50
  • I'll give the first bit a try (removing the `create()->id`), not quite sure what you mean with the second part of your comment though. Could you elaborate a bit? – Alex Jan 08 '22 at 00:40

1 Answers1

0

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.

Alex
  • 778
  • 3
  • 12
  • 27