Upgrading to Laravel 10, PHPUnit 10, and Pest 2

Search for a command to run...

No comments yet. Be the first to comment.
Testing is almost always difficult when developing applications that interact with external services, APIs, or complex features. One way to make testing easier is to use stub classes. Here's how I usually work with them. Quick intro on benefits Stubs...

GitHub's 2000 minutes per month of free usage of GitHub Actions is usually enough to handle light workflows. However, as more tech is run automatically, we are forced to upgrade to a paid plan or optimize our workflows. Currently, I am running Rector...

A nice refactoring I've learned recently is to utilize types in conditionals. For years I’ve been doing if (!empty($posts)), but Rector, a tool to automatically refactor code, changed that to if ($posts === []). The main advantage of using $posts ===...

Laravel 10 is making the headlines these days. However, many apps are stuck in older versions, or at least still use the syntax and methods of older releases. One of the most significant changes introduced in Laravel 9 is the syntax for accessors and...

We're upgrading from Laravel 9 to 10, and PHPUnit 9 to 10, and, if all goes well, we'll top it off by migrating to Pest, the latest version. To keep the positive vibes on I'll skip an upgrade from PHP 8.1 to 8.2, just because I don't want to restart Docker in the production server for now. Sunday is for safe stuff only.
Firstly, check the dependencies guide here, and complete all the steps outlined in that section. You need to update the composer.json with the new versions, and then run composer update. It will fail, most probably. The error logs will show you which packages are incompatible or need to be updated to support the latest version.
I was using some packages that were deprecated or didn't have support for Laravel 10 as of now. Luckily, in my very specific case, I didn't really need or use them, so I just removed them. The two packages were:
CORS Middleware for Laravel - all supported Laravel versions now include the CORS middleware in the core.
Laravel Slack - helper package to make Slack notifications easier to send. I wasn't making good use of this package, and Laravel has built-in logging to Slack anyway.
After you've handled this step according to your specific case, you should now have a clean installation of Laravel 10. I'd make a commit for it.
Rector will help us migrate to the new Laravel version with practically no effort. Follow the Composer installation guide here, and then install the Laravel extension. Among other configurations you might have in your rector.php file, add the Laravel 10 set:
use Rector\Config\RectorConfig;
use RectorLaravel\Set\LaravelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->sets([
LaravelSetList::LARAVEL_100,
]);
};
Now just run vendor/bin/rector process and see what happens. It will handle most of the changes for you, like model "dates" property, the Bus facade's dispatchNow to dispatchSync, etc. At this point, I make another commit to separate the automatic changes from my own changes.
As a final step, take a good look at the upgrade guide for anything that needs your attention. There are certain things that Rector can not cover for you, like moving lang files around. You're not entirely out of luck though, there's a paid service called Shift, that has that capability and is tailored for Laravel projects. I suggest you give it a chance to prove itself, it's worth every penny. But still, double-check with the upgrade guide and see if anything's missing.
Make another commit for it if you've made any changes, and run your tests. Hopefully, everything's green.
Update your PHPUnit version to "phpunit/phpunit": "^10" and run a composer update. It will probably go smoothly. The first time you'll run your tests, it will ask you to update your phpunit.xml schema to a newer version, you should run a command for that. Run it: ./vendor/bin/phpunit --migrate-configuration.
In your rector.php file add the PHPUnit rule:
use Rector\Config\RectorConfig;
use Rector\PHPUnit\Set\PHPUnitSetList;
use RectorLaravel\Set\LaravelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/app',
__DIR__ . '/database',
__DIR__ . '/tests',
]);
$rectorConfig->sets([
LaravelSetList::LARAVEL_100,
PHPUnitSetList::PHPUNIT_100,
]);
};
Don't forget to include your tests directory in the paths that Rector will look for. Run ./vendor/bin/rector again and see what happens. All your tests will be migrated to the new Attributes syntax, and your data providers will be made static. If you run your tests now they should all pass, unless you've written some suspicious test classes and used $this inside your data providers, as I did 😀. And now's the time for another commit.
Pest has a migration tool of its own, it's called Drift, and it's a Pest Plugin. You can install it with composer require pestphp/pest-plugin-drift --dev. To convert all your tests you simply run ./vendor/bin/pest --drift. It will remove all classes of your tests, rename the tests to use spaces instead of snake case or camel case, and convert most of the assertions to the expectation APIs.
Depending on your test structure, there might be some more work involved. For example, you might have tests in directories other than Feature or Unit, like Billing, etc. You can either specify the directories one by one in your Pest.php file, or include the whole directory to use the correct TestCase with a single line:uses(\Tests\TestCase::class)->in('./');.
The migration will remove all classes in your tests, so if you had private helper methods, they will be converted to functions. Inside those functions, you lose access to the $this variable, so you must check each case and fix it manually, at least at the time of this post. For example, instead of $this->mock(...) you have to use just mock(...).
And now you should be good to go. Probably all or most of your tests are green.