$this->getThoughts()

Custom Pest expectations for Inertia

I recently converted a Laravel project from PHPUnit to Pest[↗]. The project is built using Vue, Inertia and Laravel and so our tests contained a lot of assertInertia assertions.

That code looks something like this:

use Inertia\Testing\AssertableInertia as Assert;

$response = $this->get('/');

$response->assertInertia(fn (Assert $page) => $page
    ->component('Some/View')
    ->where('data', [
        'some' => 'value'
    ]));

This never really sat right with me. For a start there's a lot of standard code that was repeated throughout our codebase, including the ugly alias at the top of every file.

Looking a the Pest docs I saw it's possible to create your own custom expectations[↗]. I figured an API like this would be better:

$response = $this->get('/');

expect($response)
    ->toBeInertiaView('Some/View')
    ->where('data', [
        'some' => 'value'
    ]);

The code is shorter, is more easily readable and the alias is gone.

After a lot of fiddling with the various internal workings of the Inertia assertion, I ended up with the following three custom expectations:

use Inertia\Testing\AssertableInertia as Assert;

// Assert response is an Inertia page template
expect()->extend('toBeInertiaView', function (string $view) {
    // Prevent unhelpful 'Not an Inertia response' test fail message
    $this->value->assertOk();
    $this->value->assertInertia(fn (Assert $page) => $page->component($view));
});

// Assert Inertia response matches given array exactly
expect()->extend('where', function ($key, $data = null) {
    if (is_array($key)) {
        $this->value->assertInertia(fn (Assert $page) => $page->whereAll($key));
    } else {
        $this->value->assertInertia(fn (Assert $page) => $page->where($key, $data));
    }
});

// Assert Inertia response contains array data
expect()->extend('containing', function (string $key, array $data) {
    $this->value->assertInertia(fn (Assert $page) => $page->has($key, function ($page) use ($data) {
        foreach ($data as $key => $value) {
            $page->where($key, $value);
        }
        $page->etc();
    }));
}

The first expectation just tests that the Inertia view is correct (and prints a stack trace for any unexpected exceptions with the assertOk()).

The second expectation mimics the original where() method of the Assert class, but adds the option to test a whole array of data in one go instead of repeating the where multiple times.

The last expectation was the most difficult to get working but it allows checking that a response contains a specific key and that the provided value matches that key in the response.

Bonus

The project I was working on also makes use of a lot of Inertia lazy loading. During the refactor when implementing the above code I also noticed a lot of lazy load headers being use during testing:

$response = $this->withHeaders([
    'X-Inertia-Partial-Component' => 'Some/View',
    'X-Inertia-Partial-Data' => 'prop'
])->get('/');

So I made a quick public method in the base test class so I wouldn't have to keep writing out the header names each time. Now our lazy loading tests look like this:

$response = $this->withLazyLoadHeaders('Some/View', 'prop')->get('/');

A nice little readability improvement, if I do say so myself.

Looking at it now you could perhaps even combine the get() into the withLazyLoadHeaders() method, as I can't think of a scenario when you would want to lazy load data that isn't a GET request... Here's to the next refactor.

#inertia #pest #php #phpunit #refactoring #testing #unit tests