Laravel Livewire Key aspects for Integration or Unit testing

Video thumbnail

In Livewire we can also create tests just like we do with Unit and Integration Tests in base Laravel; its structure is the same: a class with a test extension so that it's understood that it's a test that extends TestCase:

tests/Feature/Blog/IndexTest.php

namespace Tests\Feature\Blog;

// use Illuminate\Foundation\Testing\RefreshDatabase;
// use Illuminate\Foundation\Testing\WithFaker;
// use PHPUnit\Framework\TestCase;

use Livewire\Livewire;
use Tests\TestCase;

use App\Livewire\Blog\Index;
use App\Models\Post;

class IndexTest extends TestCase
{
   /**
    * A basic feature test example.
    */
   public function test_index(): void
   {
       $this
           ->get(route('web.index'))
           ->assertSeeLivewire(Index::class)
           ->assertStatus(200)
           ->assertSee("Post List")
           // ->assertViewHas('posts', Post::paginate(15))
           // ->assertViewIs('livewire.blog.index');
       ;
   }
}

It is essential that the TestCase class appears in our test files. This allows us to use the set of GET, POST, PUT, and DELETE methods to make requests to the routes in our application.

I'm not talking about the functionality of requests to synchronize properties and events in Laravel Livewire

It's important to clarify that I'm not referring to Laravel Livewire's internal workings for synchronizing properties, but rather to testing our application's components. By default, whether you use Pest or PHPUnit, both have a class called TestCase. However, there's a crucial difference in the imports:

// use PHPUnit\Framework\TestCase;
use Tests\TestCase;

Laravel incorporates these methods through its own framework's class nesting. Thanks to this, we can evaluate components in multiple ways. For example, we can use assertions to determine if a component is being used internally or if we are correctly using form elements, such as the button component in Livewire.

Compared to Inertia, which is somewhat more closed because tests are usually done directly in Vue, Livewire offers richer options for server-side testing.

Livewire::test(Index::class)
  ->assertSee("Post List")
  ->assertViewHas('posts', Post::with('category')

Getting Started with Testing in Laravel Livewire

Video thumbnail

Let's begin by creating our test for the Blog module, specifically for the Index. The first and most important thing is to run the test command and verify that everything passes correctly:

$ php artisan test

If something goes wrong, we need to identify the problem before proceeding. For this exercise, I'll perform the minimum necessary tests. We could test different filters, but to keep things simple at the beginning, we'll just test access without any filters applied to verify that the list of posts and categories is displayed.

<?php
namespace Tests\Feature\Blog;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;

// use PHPUnit\Framework\TestCase;

use Tests\TestCase;

class IndexTest extends TestCase
{
    public function test_example(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

First test with Laravel Livewire 3

Video thumbnail

In a routine test using basic Laravel, we would start by checking a 200 status code, the route name, and the data passed to the view. However, when using Livewire, the logic changes slightly.

Recommendation: Before starting, duplicate your current database (copy and paste the .sqlite file or create a backup) to avoid losing your current test data. We will configure a specific database for the testing environment later.

Identifying the Route

We need to clearly identify the route to test (in this case, /blog). If you try to access an invalid route, you'll get a 404 error; if you were expecting a 200 and receive a 404, you'll know the problem lies in the route definition.

Here's the initial test outline:

    public function test_example(): void
    {
        $this->get(route('web.index'))->assertStatus(200);
    }

Difference Between Feature and Unit Tests

It's vital to inherit from Tests\TestCase. If you accidentally inherit from PHPUnit's inner class, the $this->get() method won't exist, and the test will fail.

These are Feature tests, not Unit tests, because we're not testing an isolated module or a simple mathematical function; we're testing the entire blog module as a whole, including its components and its HTTP response.

 $this->get(route('web.index'))->assertStatus(200);

When performing tests, it's crucial to specify which HTTP method you'll use. In this example, we'll make a GET request. For this to work, the test class must inherit from Tests\TestCase.

Imports are often confusing because there are two classes with the same name. Note the difference:

  • Incorrect: PHPUnit\Framework\TestCase (Comes from the PHPUnit core).
  • Correct: Tests\TestCase (Laravel's own class).
// use PHPUnit\Framework\TestCase;


use Tests\TestCase;

If you mistakenly use PHPUnit's test case, the test will fail because it won't find the necessary methods to make requests. While Tests\TestCase inherits the basics from PHPUnit (or Pest, depending on what you're using), it also adds vital Laravel features, such as HTTP request handling.

Feature Tests vs. Unit Tests

Using these requests is essential for us, as our goal is to test the entire module. We're not evaluating isolated functions, but rather our component-based module and the application as a whole.

For this reason, these are Feature (functional) tests, not Unit tests. That's why they're organized in the Feature folder, since we're validating the application as a whole—specifically, in this case, the Blog module.

Base Testing in Laravel Livewire 4

Video thumbnail

A key aspect is that we can use named routes, taking advantage of all the benefits this offers (as we saw in the Laravel Basics course).

If we decide to change the blog URL to something like /block, we won't have to modify our tests, since they are referenced by their technical name, for example, web.index. By using the route() function, the test automatically adapts to the change.

public function test_index(): void {
  $this->get(route('web.index'))
}

If we did it statically, like $this->get('blog'), any change to the URL would break the test by returning a 404 error, forcing us to manually update the code in each test file.

public function test_index(): void {
  $this->get('blog')
}

✅ Assertion Methods and 200 Status Code

When testing whether a page was found correctly, the usual practice is to check the 200 (OK) status code. Although the documentation lists the methods alphabetically and not always categorized by function, Laravel offers powerful options for evaluating:

  • Redirects.
  • HTML content.
  • Whether the user is authorized or authenticated.

The best part is that we don't have to manually navigate through the response to extract values; Laravel does this automatically using its assertion methods.

If we are using Laravel's TestCase class and want to specifically validate which view the component is loading, we can use the assertViewIs method:

public function test_index_view(): void {
   $response = $this->get(route('web.index'));

   // Validamos que la vista sea la correcta
   $response->assertViewIs('web.index');
}

This allows us to ensure that, beyond simply loading the page, the user is seeing the correct interface.

Testing 200 Status

To verify if the page was found correctly, Laravel offers several methods. Although they appear in alphabetical order in the documentation and are not always categorized by function, the most common is the one that validates the "OK" status, which is equivalent to status 200.

In addition to the basic statuses, there are variations for testing redirects or HTML content. These tools are essentially conditional, but they are not generic and only evaluate exact values. They can also apply complex logic, such as verifying if a user is authorized or authenticated. The best part is that Laravel does all this automatically and for free through its assertion methods, without us needing to manually navigate through the response to extract data.

Specific Assertions for Livewire

If we are using Laravel's TestCase class and want to validate which view is loading, we use the assertViewIs method. Here we can chain together multiple tests to verify the behavior of our component:

Livewire::test(Index::class)
    ->assertSee("Post List")
    ->assertViewHas('posts', Post::with('category')
        ->where('posted', 'yes')->paginate(15))
        ->assertViewIs('livewire.blog.index')

Sometimes, we need to see exactly what the response contains or what data is being delivered to the view. To do this, we can pass a callback to the assertViewHas method and perform a data dump using dd().

->assertViewHas('posts', function ($posts){
    dd(Post::paginate(15));
     dd($posts);
})

However, caution is advised: when using `dd()` on the entire page, the content is often so extensive that the console truncates it, which can make it difficult to read. Although the purpose of this class is to experiment and see what tools are available, this method is very useful for debugging to check if the data in the collection matches expectations.

Evaluating important parameters

We can also use Livewire::test() to evaluate a Laravel Livewire component:

***
use Livewire\Livewire;

class IndexTest extends TestCase {
  public function test_index(): void
   {
       $this
           ->get(route('web.index'))
           ***
       ;

        Livewire::test(Index::class);
   }
}

And from here, we can use the assertion methods that we couldn't use before:

tests/Feature/Blog/IndexTest.php

***
use Livewire\Livewire;

class IndexTest extends TestCase {
  public function test_index(): void
   {
       $this
           ->get(route('web.index'))
           ***
       ;

         Livewire::test(Index::class)
           ->assertSee("Post List")
           ->assertViewHas('posts', Post::with('category')
               ->where('posted', 'yes')->paginate(15))
               ->assertViewIs('livewire.blog.index')
       ;

   }
}

We will give an introduction to testing specific to Laravel Livewire by introducing its main assertion methods.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español