inertiajs/inertia-laravel Testing Helpers
GREAT NEWS!: This package will be merged into inertiajs/inertia-laravel on March 1st, 2021, with upgrading efforts only taking a couple of seconds. Once this happens, this package WILL remain available for installation, but WILL NOT receive any further (security) updates going forward.
Installation
You can install the package via composer:
composer require --dev claudiodekker/inertia-laravel-testing
Usage
To start testing your Inertia pages, simply call the assertInertia
method on your TestResponse
responses, and chain any of the available assertions on its closure/callback argument:
$response->assertInertia(fn ($page) => $page->someInertiaAssertion());
When using this library to its fullest extent, your tests will end up looking similar to this:
use ClaudioDekker\Inertia\Assert;
$response->assertInertia(fn (Assert $page) => $page
->component('Podcasts/Show')
->has('podcast', fn (Assert $page) => $page
->where('id', $podcast->id)
->where('subject', 'The Laravel Podcast')
->where('description', 'The Laravel Podcast brings you Laravel and PHP development news and discussion.')
->has('seasons', 4)
->has('seasons.4.episodes', 21)
->has('host', fn (Assert $page) => $page
->where('id', 1)
->where('name', 'Matt Stauffer')
)
->has('subscribers', 7, fn (Assert $page) => $page
->where('id', 2)
->where('name', 'Claudio Dekker')
->where('platform', 'Apple Podcasts')
->etc()
->missing('email')
->missing('password')
)
)
);
NOTE: The above uses arrow functions, which are available as of PHP 7.4+. If you are using this library on an older version of PHP, you will unfortunately need to use a regular callback instead:
$response->assertInertia(function (Assert $page) { $page ->component('Podcasts/Show') ->has('podcast', /* ...*/); });
NOTE: While type-hinting the
Assert
isn't necessary (and will cause some minor search-and-replaceable breakage once migrating away from this package), it allows your IDE to automatically suggest the assertion methods that can be chained.
Available Assertions
Basics:
In-depth:
Reducing verbosity (multiple assertions):
Helpers:
Component
To assert that the Inertia page has the page component you expect, you can use the component
assertion:
$response->assertInertia(fn (Assert $page) => $page->component('Podcasts/Show'));
Apart from asserting that the component matches what you expect, this assertion will also automatically attempt to locate the page component on the filesystem, and will fail when it cannot be found.
NOTE: By default, lookup occurs relative to the
resources/js/Pages
folder, and will only accept matching files that have a.vue
or.svelte
extension. All of these settings are configurable in our configuration file.If you are missing any default extensions (such as those for React), please let us know which ones should be supported by opening an issue!
Disabling or enabling a single lookup
To disable this filesystem lookup on a per-assertion basis, you may pass false
as the second argument:
$response->assertInertia(fn (Assert $page) => $page->component('Podcasts/Show', false));
Alternatively, if you've disabled the automatic component filesystem lookup in the configuration file, it's possible to do the opposite and instead enable the lookup on a per-assertion basis by passing true
as the second argument.
(Page) URL
To assert that the Page URL matches what you expect, you may use the url
assertion:
$response->assertInertia(fn (Assert $page) => $page->url('/podcasts'));
(Asset) Version
To assert that the (asset) version matches what you expect, you may use the version
assertion:
$expected = md5(mix('/js/app.js'));
$response->assertInertia(fn (Assert $page) => $page->version($expected));
NOTE: We recommend to only use this assertion when you are using asset versioning.
has
Basic Usage
To assert that Inertia has a property, you may use the has
method. You can think of has
similar to PHP's isset
:
$response->assertInertia(fn (Assert $page) => $page
// Checking a root-level property
->has('podcast')
// Checking that the podcast prop has a nested id property using "dot" notation
->has('podcast.id')
);
Count / Size / Length
To assert that Inertia has a certain amount of items, you may provide the expected size as the second argument:
$response->assertInertia(fn (Assert $page) => $page
// Checking that the root-level podcasts property exists and has 7 items
->has('podcast', 7)
// Checking that the podcast has 11 subscribers using "dot" notation
->has('podcast.subscribers', 11)
);
The above will first assert that the property exists, as well as that is the expected size.
This means that there is no need to manually ensure that the property exists using a separate has
call.
Scoping
In a previous version of this library, testing code could become fairly verbose, and the deeper your assertions went, the more complex your assertions became. For instance, here is a real example of some assertion logic we used to write:
$response->assertInertiaHas('message.comments.0.files.0.url', '/storage/attachments/example-attachment.pdf');
$response->assertInertiaHas('message.comments.0.files.0.name', 'example-attachment.pdf');
Fortunately, we no longer have to do this. Instead, we can simply scope properties using the has
method:
$response->assertInertia(fn (Assert $page) => $page
// Creating a single-level property scope
->has('message', fn (Assert $page) => $page
// We can now continue chaining methods
->has('subject')
->has('comments', 5)
// And can even create a deeper scope using "dot" notation
->has('comments.0', fn (Assert $page) => $page
->has('body')
->has('files', 1)
->has('files.0', fn (Assert $page) => $page
->has('url')
)
)
)
);
While this is already a significant improvement, that's not all: As you can see in the example above, you'll often run into situations where you'll want to check that a property has a certain length, and then tap into one of the entries to make sure that all the props there are as expected:
->has('comments', 5)
->has('comments.0', fn (Assert $page) => $page
// ...
To simplify this, you can simply combine the two calls, providing the scope as the third argument:
$response->assertInertia(fn (Assert $page) => $page
// Assert that there are five comments, and automatically scope into the first comment.
->has('comments', 5, fn(Assert $page) => $page
->has('body')
// ...
)
);
where
To assert that an Inertia property has an expected value, you may use the where
assertion:
$response->assertInertia(fn (Assert $page) => $page
->has('message', fn (Assert $page) => $page
// Assert that the subject prop matches the given message
->where('subject', 'This is an example message')
// or, the exact same, but for deeply nested values
->where('comments.0.files.0.name', 'example-attachment.pdf')
)
);
Under the hood, this first calls the has
method to ensure that the property exists, and then uses an assertion to
make sure that the values match. This means that there is no need to manually call has
and where
on the same exact prop.
Model
, Arrayable
, or Responsable
transforming
Automatic Eloquent For convenience, the where
method doesn't just assert using basic JSON values, but also has the ability to
test directly against Eloquent Models, classes that implement the Arrayable
or Responsable
interfaces.
For example:
$user = User::factory()->create(['name' => 'John Doe']);
// ... (Make your HTTP request etc.)
$response->assertInertia(fn (Assert $page) => $page
->where('user', $user)
->where('deeply.nested.user', $user)
);
Using a Closure
Finally, it's also possible to assert against a callback / closure. To do so, simply provide a callback as the value,
and make sure that the response is true
in order to make the assertion pass, or anything else to fail the assertion:
$response->assertInertia(fn (Assert $page) => $page
->where('foo', fn ($value) => $value === 'bar')
// or, as expected, for deeply nested values:
->where('deeply.nested.foo', function ($value) {
return $value === 'bar';
})
);
Because working with arrays directly isn't always a great experience, we'll automatically cast arrays to Collections:
$response->assertInertia(fn (Assert $page) => $page
->where('foo', function (Collection $value) {
return $value->median() === 1.5;
})
);
etc
This library will automatically fail your test when you haven't interacted with at least one of the props in a scope.
While this is generally useful, you might run into situations where you're working with unreliable data
(such as from a feed), or with data that you really don't want interact with, in order to keep your test simple.
For those situations, the etc
method exists:
$response->assertInertia(fn (Assert $page) => $page
->has('message', fn (Assert $page) => $page
->has('subject')
->has('comments')
->etc()
)
);
IMPORTANT: This automatic property check DOES NOT APPLY TO YOUR TOP-LEVEL PROPS. If you wish to enforce this for the top-level of your page as well, you may enable this in our configuration file.
NOTE: While
etc
reads fluently at the end of a query scope, placing it at the beginning or somewhere in the middle of your assertions does not change how it behaves: It will disable the automatic check that asserts that all properties in the current scope have been interacted with.
missing
Because missing
isn't necessary by default, it provides a great solution when using etc
.
In short, it does the exact opposite of the has
method, ensuring that the property does not exist:
$response->assertInertia(fn (Assert $page) => $page
->has('message', fn (Assert $page) => $page
->has('subject')
->missing('published_at')
->etc()
)
);
Reducing verbosity
To reduce the amount of where
, has
or missing
calls, there are a couple of convenience methods that allow you to
make these same assertions in a slightly less-verbose looking way. Do note that these methods do not make your assertions
any faster, and really only exist to help you reduce your test's visual complexity.
has
Instead of making multiple has
calls, you may use the hasAll
assertion instead. Depending on how you provide
arguments, this method will perform a series of slightly different but predictable assertion:
has
usage
Basic $response->assertInertia(fn (Assert $page) => $page
// Before
->has('messages')
->has('subscribers')
// After
->hasAll([
'messages',
'subscribers',
])
// Alternative
->hasAll('messages', 'subscribers')
);
Count / Size / Length
$response->assertInertia(fn (Assert $page) => $page
// Before
->has('messages', 5)
->has('subscribers', 11)
// After
->hasAll([
'messages' => 5,
'subscribers' => 11,
])
);
where
To reduce the amount of where
calls, the whereAll
method exists.
Since this method checks properties against values by design, there isn't a lot of flexibility like with some of these other methods, meaning that only the array-syntax exists for it right now:
$response->assertInertia(fn (Assert $page) => $page
// Before
->where('subject', 'Hello World')
->has('user.name', 'Claudio')
// After
->whereAll([
'subject' => 'Hello World',
'user.name' => fn ($value) => $value === 'Claudio',
])
);
missing
Instead of making multiple missing
call, you may use missingAll
instead.
Similar to basic hasAll
usage, this assertion accepts both a single array or a list of arguments, at which point it
will assert that the given props do not exist:
$response->assertInertia(fn (Assert $page) => $page
// Before
->missing('subject')
->missing('user.name')
// After
->missingAll([
'subject',
'user.name',
])
// Alternative
->missingAll('subject', 'user.name')
);
Debugging
While writing your tests, you might find yourself wanting to inspect some of the page's props using Laravel's
dump
or dd
helpers. Luckily, this is really easy to do, and would work more or less how you'd expect it to:
$response->assertInertia(fn (Assert $page) => $page
// Dumping all props in the current scope
// while still running all other assertions
->dump()
->where('user.name', 'Claudio')
// Dump-and-die all props in the current scope, preventing
// all other (perhaps failing) assertions from running
->dd()
->where('user.name', 'Jonathan')
// Dumping / Dump-and-die a specific prop
->dump('user')
->dd('user.name')
);
Publishing the configuration file
To modify any settings such as the lookup paths, valid extensions etc., you may publish our configuration file into your application and change any of it's values. To do so, run the following Artisan command:
php artisan vendor:publish --provider="ClaudioDekker\Inertia\InertiaTestingServiceProvider"
Testing
composer test
Changelog
Please see CHANGELOG for more information what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email claudio@ubient.net instead of using the issue tracker.
License
The MIT License (MIT). Please see License File for more information.