Upgrading to Laravel 10, PHPUnit 10, and Pest 2
Tags: php, laravel, phpunit, pest
Published: 2023-09-17 15:25:30

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.

## Remove or upgrade outdated packages

Firstly, check the [dependencies guide here](https://laravel.com/docs/10.x/upgrade#updating-dependencies), 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](https://github.com/fruitcake/laravel-cors) - all supported Laravel versions now include the CORS middleware in the core.
    
* [Laravel Slack](https://github.com/gpressutto5/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.

## Install Rector for Laravel

[Rector](https://getrector.com/) will help us migrate to the new Laravel version with practically no effort. Follow the Composer installation guide [here](https://getrector.com/documentation), and then install the [Laravel extension](https://github.com/driftingly/rector-laravel). Among other configurations you might have in your `rector.php` file, add the Laravel 10 set:

```php
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`, the `Relation`'s `getBaseQuery` to `getBaseQuery`, etc. At this point, I make another commit to separate the automatic changes from my own changes.

## Add the finishing touches to the upgrade

As a final step, take a good look at the [upgrade guide](https://laravel.com/docs/10.x/upgrade#public-path-binding) 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](https://laravelshift.com/upgrade-laravel-9-to-laravel-10), 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.

## Upgrade to PHPUnit 10

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:

```php
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.

## Migrate to Pest v2

Pest has a migration tool of its own, it's called [Drift](https://pestphp.com/docs/migrating-from-phpunit-guide), 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.