Unit tests and integration in Laravel inertia
In this chapter, we are going to run tests for the project we created earlier, for each of the modules, we will create the tests not in the same order in which the modules were developed in the book, but rather, we will create the tests starting with the simplest modules such as the one for the blog.
We will use the PHPUnit testing framework to create each of the tests, but you can use Pest if you prefer since there is almost a one-to-one relationship with the assertion methods between PHPUnit and Pest.
First steps
We will start by creating the tests for the blog module, that is, for the listing and for the detail page.
We will create the test for the blog:
$ php artisan make:test Blog/PostTest
From Laravel to Inertia
We will define the following test for the listing, which will allow us to exemplify the changes between the Laravel or Inertia tests:
tests\Feature\BlogTest.php
<?php
namespace Tests\Unit\Blog;
// use PHPUnit\Framework\TestCase;
use Tests\TestCase;
class PostTest extends TestCase
{
public function test_index(): void
{
$this->get(route('web.index'))
->assertViewIs('blog.show');
->assertStatus(200);
}
}
The above test allows us to verify that the list of posts in the index returns a status code 200.
We execute:
$ php artisan test
And we will see a result like:
Unable to locate file in Vite manifest: resources/js/Pages/Blog/Index.vue. (View: C:\Users\andre\Herd\inertiastore\resources\views\app.blade.php)
In which, it tells us that we must have the files generated by vie enabled, so, or enable development mode:
$ npm run dev
Or you can generate the files to production:
$ npm run prod
If you run the tests again, you will see that we now have an error like the following:
+++ Actual
@@ @@
-'blog.show'
+'app'
This is because the assertViewIs() method is used to verify blade views and not components in Vue, based on the previous error, you can see that inertia internally when using the inertia() method to return a component, it uses a view called app:
->assertViewIs('app')
If you run it again, you will see that the previous error no longer occurs, but leaving an app view defined that we are not using, since we are using the components in Vue, does not make sense. This is where the assertions created specifically for Inertia come in, which we will see in the next section.
Assert Inertia
In Inertia, we have specific assertions to work with Vue components, to do this, we must import at the test file level:
tests\Unit\Blog\PostTest.php
// use PHPUnit\Framework\TestCase;
use Tests\TestCase;
use Inertia\Testing\AssertableInertia as Assert;
class YourTest extends TestCase
public function test_test(): void
{
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => dd($page)));
}
}
In the listing test, in whose controller we use the Assert component, we must configure it as follows at the test level:
tests\Unit\Blog\PostTest.php
<?php
namespace Tests\Unit\Blog;
// use PHPUnit\Framework\TestCase;
use Tests\TestCase;
use Inertia\Testing\AssertableInertia as Assert;
class PostTest extends TestCase
{
public function test_index(): void
{
$this->get(route('web.index'))
// ->assertViewIs('app')
->assertStatus(200);
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => dd($page)
->component('Blog/Index')
->has('posts', fn (Assert $page) => $page
)
);
}
}
And we will see that if we run the test, it passes; there are many verifications that can be performed at the level of Inertia assertions as you can see in the official documentation:
https://inertiajs.com/testing
Although the syntax is a bit strange, let's go step by step to know exactly how it is structured, let's evaluate the object called $page to see what it provides:
->assertInertia(fn (Assert $page) => dd($page)
When running the test, we will see output like the following:
Inertia\Testing\AssertableInertia {#2397
-props: array:14 [
"errors" => []
"jetstream" => array:11 [
"canCreateTeams" => false
"canManageTwoFactorAuthentication" => true
"canUpdatePassword" => true
"canUpdateProfileInformation" => true
"hasEmailVerification" => false
"flash" => []
"hasAccountDeletionFeatures" => true
"hasApiFeatures" => true
"hasTeamFeatures" => true
"hasTermsAndPrivacyPolicyFeature" => true
"managesProfilePhotos" => true
]
"auth" => array:1 [
"user" => null
]
"errorBags" => []
"flash" => array:1 [
"message" => null
]
"step" => 1
"cart" => []
"posts" => array:13 [
"current_page" => 1
"data" => array:15 [
0 => array:13 [
"id" => 2
"title" => "Post 5111"
"slug" => "post-4"
"date" => "2024-08-22"
"image" => "1729333215.png"
"text" => "asasasasas"
"description" => "asasasas"
"posted" => "not"
"type" => "course"
"category_id" => 1
"created_at" => "2024-08-18T09:54:24.000000Z"
***
"category_id" => 2
"created_at" => "2024-09-21T09:43:12.000000Z"
"updated_at" => "2024-09-21T09:43:12.000000Z"
"category" => array:7 [
"id" => 2
"title" => "Cate 2"
"slug" => "cate-2"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:19.000000Z"
"updated_at" => "2024-08-15T10:08:19.000000Z"
]
]
]
"first_page_url" => "http://inertiastore.test/blog?page=1"
"from" => 1
"last_page" => 47
"last_page_url" => "http://inertiastore.test/blog?page=47"
"links" => array:15 [
0 => array:3 [
"url" => null
"label" => "« Previous"
"active" => false
***
"active" => false
]
]
"next_page_url" => "http://inertiastore.test/blog?page=2"
"path" => "http://inertiastore.test/blog"
"per_page" => 15
"prev_page_url" => null
"to" => 15
"total" => 702
]
"categories" => array:2 [
0 => array:7 [
"id" => 1
"title" => "Cate 1"
"slug" => "category-1"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:12.000000Z"
"updated_at" => "2024-08-15T10:08:12.000000Z"
]
1 => array:7 [
"id" => 2
"title" => "Cate 2"
"slug" => "cate-2"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:19.000000Z"
"updated_at" => "2024-08-15T10:08:19.000000Z"
]
]
"prop_type" => null
"prop_category_id" => null
"prop_from" => null
"prop_to" => null
"prop_search" => null
]
-path: null
#interacted: []
-component: "Blog/Index"
-url: "/blog"
-version: "b05311e78830e9fb34e382b9802ceab2"
The output above was recalled to avoid filling 4 pages with data that we are not going to evaluate in this guide, but the reader is encouraged to test it and evaluate the result.
We will see that the output above corresponds to the global object called $page which is the one we used previously to obtain data about the page, such as the Vue component used, props, shared data, user data, and more:
resources\js\Pages\Blog\Index.vue
{{ $page }}
The data supplied depends on the resource we want to evaluate, since just like the tests in basic Laravel, everything depends on how the resource or controller to be evaluated is formed.
Video Transcription
Review of Unitary Tests
In this section we are going to work with the tests in Laravel inertia, it is that it is the next block we have here before the closures would consider you that at least you see this class then good you decide what you want to do but I would recommend you greatly That I implement tests at the level of your application and it is a bit what I am going to enter in this section apart from that I also remind you that this course/book Laravel inertia in this case has been as the next level is to say I already have a course From the basic Laravel in which I invested more than 7 hours I think it was about 8 hours to the date doing evidence explaining why and everything else.
Therefore I already did that work in this case or in that case it was to explain the tests the basic the basic here we do not have many changes but it is important that you understand that I already assume that at least you have that knowledge for what I am going to go a little further in case you miss a little also I have some resources on my YouTube channel but again that for me is a settled issue that is part of basic Laravel that are the requirements that you have to have Or you had to have to take this course so clarified is a bit what I want to do here is a small review.
As much as it is for that, I will do a review here, simply reading some t these keys in my book in this case it would be the basic Laravel book for what is commented before this section that we have here will be free at any time to pause the class and Reading the fragments of the book and I will be taking important parts starting with what I want is Being a guarantee that the application works correctly and that the application is of quality in other words is generally a software quality indicator when implementing the application, that is, we do not have a blessed disaster there that what we implement Tests to try it and when we begin to implement the tests in case you have not done many you will see a little meaning to all this but it is precisely how they implement the same there we can have several tip of what we are implementing and If the implementation we made is really of quality or not another important point is like that something negative with which the application passes all the evidence that implements does not mean that it is free of errors simply that we possibly do not create sufficient tests for Detecting errors but no matter how much it indicated you is an indicator of software quality that our application is at least well basically although everything depends on how many tests you want to perform we are simply to do a handful of EL sim simply to exemplify The process then is a bit here what basically try everything we implements usually focus more is the part of the controller.
What to try
That is to say, component controllers, whatever we are using aid functions in case they would be good candidates and specifically everything that we are going to perform requests types type Get post delete all that should be tested in other words in other words are I could say what we have here implemented within the app we can also try the view but at least I have not planned to cover in this section then in Laravel for anyone we have several ways to do the same then we can use PHP Unit that has been the Classic approach I could consider it like this or the most modern in quotes because in the final they are very similar to be the dependent that offers us a syntax a little cleaner and more elegant I in this section I will use pHP Unit and as I told you and me and In the basic course I did both pHP Unit and then compare them we should therefore have in case you have chosen pest as Framework for Testing when Locast is the project you could perfectly adapt them but these two exist as I tell you are very similar There is a lot of difference in that then we have two folders that are the feature folders and the folder called Unit in this case this refers to the integration tests that are the ones we have here as are the controllers or those components that You cannot try them individually but do a lot of operations and in this case the unit tests that we will not focus on in this course more than everything will be in this to test the controllers are those components that you can like who says to divide or That I do not forgive that you cannot divide them as a model a method of a help function I think it is a good example of this that is simply an operation that you are performing while the controllers are more generic because they connect to models are connected The disc do several operations while usually what a facade is a model are more indivisible structures, it could then be said based on that you have to decide where you are going to place it as I say when we are already working with the controllers, it is usually placed here in The feature folder although that is simply an organization then to run the tests we have of what you use if you are using pHP Unit you can place:
$ vendor/bin/phpunit
And if you are using pest you can use:
$ vendor/bin/pest
Or directly run:
$ php artisan test
We have the Make Test command here you can place you in the folder game as well as the controllers:
$ php artisan make:test <ClassTest>
Unitary and Integration Tests
And others if you want it to be a unitary test you can place here the option of less unit so here we had a test that was the first thing we did some mathematical operations a calculator so to speak there you can see it sum subtract division and multiplication and this was The command we use to create it in this case as they are operations such as who says they cannot be divisible was a unitary test then here we placed the lesser unit option to believe the UNIT folder that was mentioned above here we would have it and From here what we did here some tests in this case we implemented another part this should not be here but it was precisely to facilitate the exercise we also implemented the layer to consume this resource that is what we wanted to try everything together in the Same file then here we simply create an instance of the object and we started to test then it was a bit commented with this I am closing.
Understanding the evidence
We have several assertion methods that are the ones we can use to try it in the same way I am reviewing that I do not want to focus so much but they are of a assertion and they are as conditional:
$this->assertTrue(true);
For example, this is the same, we have the greatest equal the same if it is whole if it is negative if it has a certain route we have a lot and that is what we are measuring here in this good case these are the most used as indicated we have many which many It is the joke here and with this I am closing as I told you at the beginning with which you perform tests is not a sufficient condition to indicate that the application is free of errors but surely that you did not do enough or not implementation tests.
$page object in assertion methods in Laravel Inertia for Unit and Integration testing
To know what we have in the $page object:
->assertInertia(fn (Assert $page) => $page
We can do a debug; so we come here and put it:
->assertInertia(fn (Assert $page) => dd($page)
And we run to see what brings us here, look at what we have to know exactly what we have here, what we can explore, it is a little why I am making this video and you can see that it is the blessed $ page that we have had all our lives, that we have had all our lives here, look, the pagination is returning to us, it is very long, it is like four pages, this would be for the content that we are passing to it. We have already reached the publications, if we continue going up a little faster that is what we have, then from this you can already understand what you can explore and what you cannot explore, for example, if you pass an incorrect form then you know that you have the error that you can explore the String we have is information, well this would be from the user, what it can be, the user, here we have the user, I am not authenticated and that is why the flash appears in this way, which we configure among others:
Inertia\Testing\AssertableInertia {#2397
-props: array:14 [
"errors" => []
"jetstream" => array:11 [
"canCreateTeams" => false
"canManageTwoFactorAuthentication" => true
"canUpdatePassword" => true
"canUpdateProfileInformation" => true
"hasEmailVerification" => false
"flash" => []
"hasAccountDeletionFeatures" => true
"hasApiFeatures" => true
"hasTeamFeatures" => true
"hasTermsAndPrivacyPolicyFeature" => true
"managesProfilePhotos" => true
]
"auth" => array:1 [
"user" => null
]
"errorBags" => []
"flash" => array:1 [
"message" => null
]
"step" => 1
"cart" => []
"posts" => array:13 [
"current_page" => 1
"data" => array:15 [
0 => array:13 [
"id" => 2
"title" => "Post 5111"
"slug" => "post-4"
"date" => "2024-08-22"
"image" => "1729333215.png"
"text" => "asasasasas"
"description" => "asasasas"
"posted" => "not"
"type" => "course"
"category_id" => 1
"created_at" => "2024-08-18T09:54:24.000000Z"
***
"category_id" => 2
"created_at" => "2024-09-21T09:43:12.000000Z"
"updated_at" => "2024-09-21T09:43:12.000000Z"
"category" => array:7 [
"id" => 2
"title" => "Cate 2"
"slug" => "cate-2"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:19.000000Z"
"updated_at" => "2024-08-15T10:08:19.000000Z"
]
]
]
"first_page_url" => "http://inertiastore.test/blog?page=1"
"from" => 1
"last_page" => 47
"last_page_url" => "http://inertiastore.test/blog?page=47"
"links" => array:15 [
0 => array:3 [
"url" => null
"label" => "« Previous"
"active" => false
***
"active" => false
]
]
"next_page_url" => "http://inertiastore.test/blog?page=2"
"path" => "http://inertiastore.test/blog"
"per_page" => 15
"prev_page_url" => null
"to" => 15
"total" => 702
]
"categories" => array:2 [
0 => array:7 [
"id" => 1
"title" => "Cate 1"
"slug" => "category-1"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:12.000000Z"
"updated_at" => "2024-08-15T10:08:12.000000Z"
]
1 => array:7 [
"id" => 2
"title" => "Cate 2"
"slug" => "cate-2"
"image" => null
"text" => null
"created_at" => "2024-08-15T10:08:19.000000Z"
"updated_at" => "2024-08-15T10:08:19.000000Z"
]
]
"prop_type" => null
"prop_category_id" => null
"prop_from" => null
"prop_to" => null
"prop_search" => null
]
-path: null
#interacted: []
-component: "Blog/Index"
-url: "/blog"
-version: "b05311e78830e9fb34e382b9802ceab2"
Understanding what we have, you will be fully capable of making these conditions that I will explain to you later a little about what these methods are for, but for now I want to explain to you everything behind it. So what you can see here is what they are evaluating, what happens; at least for now we can check that it is the part that exists we could easily check it here I think I have one below here I have to clean the output here is the presentation of the page object something like that we could evaluate So this already getting ahead a little bit already clarified this again and we see what we have that this you can also see here but you have to consir what we are passing here at the component level all this and I am talking right now about the post object but down here after this it has to appear what happens is that it costs a little bit here the reading should appear the categories prop type and all these parameters that we are passing here I am going to try to find it I think it is at the end here is the category another array and here we have the rest of those indicated so in order not to leave this so cut off since the objective of this video is simply to see the page object I am going to leave it here commented so that you can evaluate it or compare the code we are going to return to this that has to work without problems.
From Laravel to Inertia in Unit and Integration Testing AssertableInertia 3
We stay here in which we are checking which is the page that is being returned, we stay in which we cannot directly place which is the component that we are returning, which for this example would be the first one here, the Index one, the Index block one, well, in this case we have to place something as generic as app that tells us absolutely nothing since it is not data that we are configuring but it would be handled internally by inertia and with this we cannot access what data we are passing to it either:
$this->get(route('web.index'))
->assertViewIs('app');
->assertStatus(200);
If we are passing a post, the rest of the parameters etc., which is a problem, then as I was telling you here we cannot use the basic structure that we have in Laravel of the assertion methods, but we have to go a little further and use some functions that Inertia allows us or that Inertia specifically incorporates, we have to import the following class and then here we have one called make inertia as I told you we have a new method in which it has this somewhat strange syntax, it is simply an arrow function and from here you can chain some operations just as you can see here for example it could be has that you can use in various ways as you will see little by little but this is also indicated to you here in the official documentation that is not entirely Clara since it seems a little generic to me but here you can also see an example Note that with make inertia you can access what the component is, which is the component that you are processing and methods like has and we have several there:
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => $page));
I can show you right now but we have them here for now don't worry too much about the methods although specifically the has one that does like three things but the has one of the operations it does is verify if it exists and it also counts the lengths, that is to say it's like an iset if it's established and the deware just like what happens with the ween elen clauses Search for a fixed value, that is to say it's an Equals equals Do other little things like I told you that you can see by looking around here but we'll cover it little by little for now the only thing I want to do is good that at least the blessed component is evaluated:
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => dd($page)
->component('Blog/Index'));
Parameter page
En la documentación oficial estamos en empleando la sintaxis de una función de flecha de php, colocamos el parámetro que se llama page le podemos colocar el tipo y por aquí fíjate que no lo veo Qué raro que no sale porque el paquete es inertia testing assemble inertia pero a mí al menos no me aparece Bueno entonces me toca importarlo aquí de manera manual voy a colocarlo por aquí arriba coloco use inertia coloco testing coloco hacer acert inertia y aquí le coloco el alias de acer bueno por eso es que no aparece porque tiene un alias y este es el que emplearía por acá y ahí sí lo está encontrando perfectamente entonces importanto esto que haga la navegación o simplemente colocarías aquí assertInertia supongo pero voy a dejarlo así bien por aquí esto es una función de flecha tiene sentido aquí porque no vamos a imprimir nada todo es llamar un montón de métodos y aquí recibimos el o retornamos el objeto de page aquí lo tenemos como parámetro y aquí lo retornamos y a partir de aquí mediante este objeto llamado page podemos hacer algunas comprobaciones así que en en este caso esto va aquí adentro aquí colocamos bueno para que quede organizado sería así coloco un punto y coma que me estoy comiendo creo que me falta un paréntesis okay así sí creo voy a ejecutar perfecto ahí pasa es importante que hagas esto para que te familiarices un poco con la sintaxis y aquí en el page es que le colocarías el resto de los métodos fíjate que si colocas aquí la flechita tenemos muchas cosas hay cosas que son así genéricas acá del autocompletado pero por algún lugar en la vida están algunos métodos que podemos ampliar como el de has y el de where o el de component.
has() method
From here we are going to use a method that we still don't know very well what it does but as I was telling you, it does several things among them is an iset or the check that the iset does, if it exists, let's see if this returns something that indicates whether it exists or not and notice that it happened successfully again to know if this is really doing something or is playing with us, we put an incorrect parameter and here we should see a nice error. Therefore you already know that this does not exist and here it simply tells you that it does not exist, it does not give you one as if to say what it should be because it is not capable of making a match, so this is another type of error that can occur to you, so we correct it again being very clear that what we put here are the parameters that we are supplying to our component, we are going to leave it here and in the next class we continue exploring these methods a little at this point we are using Inertia through the page object whose parameter is this one that we brought before. We already know a little about how its syntax works, which is an arrow function here:
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => $page
This would be what we are returning and this bunch of methods that we are placing here is about the page object that we already evaluated and you already know how to evaluate it and from here what data we have there.
Since in this case it is php and here I already believe but it has the same Data that it is important that you understand that it is the same object And from there understanding that in the page object we have basically not only the information of the page but also the user among other things of inertia share and other things:
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => $page
->component('Blog/Index')
->has('posts', fn (Assert $posts) => dd($posts)
It is easy to understand what we have here to work with and to obtain the parameters since it is part of the page object, but there you can print it as we have done previously in the course. Well, now that this is clear, we are going to evaluate a little what the has method is, which we have several implementations of and it is again criticizing a little the official documentation that seems very imprecise to me, since notice that here we are using has, notice that I am using it here without any parameter, here they are using it with a parameter and they are also using it to implement a callback:
->has('seasons.4.episodes')
->has('seasons.4.episodes', 21)
->has('host', fn (Assert $page) => $page
->where('id', 1)
This is to know if this account exists, the amounts as a second parameter and also as a second parameter you can return a function with which you can continue evaluating things, in this case, for example, as we already have the post, which is the page, here we could also evaluate with another method that we will see in the next class, but to go messing everything up here, notice that we have a parameter at the end called or at the beginning, I don't know where, called last page, which I can't find. Let's see, here we have last page.:
$this->get(route('web.index'))
->assertInertia(fn (Assert $page) => $page
->component('Blog/Index')
->where('categories', Category::get())
->where('prop_from', null)
->where('posts', Post::with('category')->paginate(15))
->has('prop_from')
->has('posts', 15)
->has('posts', fn (Assert $page) => $page
->where('last_page', 47)
where() method
We already know how to use the has() method, in this case in a few words it could be summarized as being to verify the existence of some property, some prop that we have defined, but what happens if we want to check the value directly, which is more interesting, of course, in this case we have the where that can be said to work in the same way as the where in Eloquent, that is, it compares by value, whether it is an integer, a String, a rate, a pagination, anything, and here you can see an example of what we are going to do.
So this you have to, as they say, match what we have here, so here we can see if we go up a little, we are going to start with the simplest thing, which would be the categories, I assume, so the category is nothing more than an array, a list of categories, so to speak:
$this->get(route('web.index'))->assertInertia(
fn(Assert $page) => $page
->component('Blog/Index')
***
->where('categories', Category::get())
What we have to place in this case would be exactly here. Remember that the order does not matter if you place it above or below, it is exactly the same.
I agree to receive announcements of interest about this Blog.
We will see the first steps with Laravel inertia in the creation of unit and/or integration tests.
- Andrés Cruz
This material is part of my complete course and book; You can purchase them from the books and/or courses section, Curso y Libro primeros pasos con Laravel 12 Inertia 2 + Vue.js y Tailwind.css - 2025.