DEV Community

Cover image for Upgrading to Pest 5 and making --tia the default
Nasrul Hazim
Nasrul Hazim

Posted on

Upgrading to Pest 5 and making --tia the default

TL;DR

  • Bumped Kickoff (my Laravel scaffolder) to Pest 5, PHP 8.4 floor, Symfony Console 8.
  • Made pest --tia the default composer test — only re-run tests affected by your change.
  • Three real gotchas surfaced: Symfony Console's add() is gone, TIA needs real tests to exist, and a Redis throttle leaked state across runs and threw random 429s.

Kickoff is the tool I use to bootstrap new Laravel projects with sane defaults. Today it moved to Pest 5, and I took the chance to turn on test impact analysis everywhere. Here's what actually mattered.

TIA: run the tests your change touched, nothing else

Test Impact Analysis maps which tests exercise which code, then on the next run only executes the ones your diff could have broken. On a small package it's a nicety; on a big app it's the difference between a 4-second loop and a 4-minute one.

Turning it on is one flag:

"scripts": {
    "test": [
        "@php vendor/bin/pest --tia"
    ]
}
Enter fullscreen mode Exit fullscreen mode

The catch: TIA only helps if you have real tests it can map. Which led to the less obvious work below.

Gotcha 1: Symfony Console 8 dropped add()

Pest 5 allows Symfony Console ^8, and Console 8 removed the long-deprecated Application::add(). If your CLI registers commands the old way, it just breaks:

// before
$app->add(new StartCommand());

// after
$app->addCommand(new StartCommand());
Enter fullscreen mode Exit fullscreen mode

Small change, but it's a hard failure on boot, so worth knowing before you bump.

Gotcha 2: ship real tests, not example stubs

TIA is useless against placeholder tests. So the scaffolder now removes the starter kit's example tests during setup and ships Pest conversions of the real ones — auth flows, dashboard — so a freshly generated project has a meaningful test map from commit one.

test('registration screen can be rendered', function () {
    $this->get('/register')->assertOk();
});

test('new users can register', function () {
    $this->post('/register', [
        'name' => 'Test User',
        'email' => 'test@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ])->assertRedirect(route('dashboard', absolute: false));

    $this->assertAuthenticated();
});
Enter fullscreen mode Exit fullscreen mode

Gotcha 3: the Redis throttle that leaked 429s

This one cost me the most time. A rate-limit throttle backed by Redis kept its counter across test runs — so a suite that hammered a throttled route would start returning 429 Too Many Requests on the next run, seemingly at random. The fix is to only reach for the Redis-backed throttle outside testing:

->withMiddleware(function (Middleware $middleware) {
    if (! app()->environment('testing')) {
        $middleware->throttleWithRedis();
    }
})
Enter fullscreen mode Exit fullscreen mode

Lesson: any middleware holding state in an external store is a test-isolation hazard. Either reset it between runs or don't wire it in tests at all.

While I was in there: laravel/doctor

Generated projects now ship with laravel/doctor as a dev dependency — a quick environment/health check so a new project can tell you what's misconfigured before you go hunting.

Takeaway

Change Why it matters
Pest 5 + --tia default Fast, targeted test loops
Ship real tests in stubs TIA needs a real map to be useful
Guard stateful middleware in tests Kills flaky cross-run failures
laravel/doctor in new projects Faster diagnosis of setup issues

TIA is one of those upgrades that pays back every single day — but only if your generated project starts life with tests worth mapping. That's the part worth doing properly.

Top comments (0)