Testowanie komponentów Livewire

Livewire to potężne narzędzie w ekosystemie Laravel, które pozwala na tworzenie dynamicznych i interaktywnych komponentów bez konieczności pisania dużej ilości JavaScriptu. Wraz z rosnącą złożonością aplikacji pojawia się potrzeba testowania komponentów, aby mieć pewność, że działają one zgodnie z oczekiwaniami i zachowują się poprawnie w różnych scenariuszach.

W tym artykule omówimy, jak testować komponenty Livewire – zarówno jednostkowo, jak i integracyjnie, wykorzystując wbudowane narzędzia Laravel i Livewire. Przedstawimy praktyczne przykłady kodu oraz zaproponujemy ćwiczenia, które pomogą Ci utrwalić zdobytą wiedzę.

Artykuł jest kontynuacją poprzednich wpisów dotyczących Livewire:

Podstawy testowania w Laravelu (w kontekście Livewire)

Laravel domyślnie dostarcza strukturę do testów w folderze tests, dzieląc je na:

  • Unit – testy jednostkowe (testujące głównie logikę, pojedyncze metody czy helpery).
  • Feature (lub Integration) – testy integracyjne, które mogą dotyczyć całego przepływu w aplikacji (w tym bazy danych, widoków, czy komponentów Livewire).

Dla Livewire warto wykorzystywać w szczególności testy typu Feature, aby sprawdzić zachowanie całego komponentu (czy reaguje na akcje, waliduje dane, renderuje odpowiednie fragmenty HTML).
W pliku composer.json znajduje się standardowa komenda do uruchamiania testów:

				
					composer test

				
			

lub bezpośrednio:

				
					php artisan test

				
			

lub:

				
					./vendor/bin/phpunit

				
			

Metoda Livewire::test()

Testowanie komponentów Livewire opiera się na statycznej metodzie Livewire::test('NazwaKomponentu'). Pozwala ona utworzyć instancję testową komponentu, na której można wywoływać metody, ustawiać właściwości (->set()) oraz sprawdzać wyniki (->assertSee(), ->assertEmitted(), ->assertOk(), itp.).

Prosty przykład testu może wyglądać tak (zakładając, że mamy komponent Counter):

				
					<?php

namespace Tests\Feature;

use Tests\TestCase;
use Livewire\Livewire;

class CounterTest extends TestCase
{
    /** @test */
    public function it_increments_the_counter()
    {
        Livewire::test('counter')
            ->call('increment')
            ->assertSet('count', 1);
    }
}

				
			

Wyjaśnienie:

  • Livewire::test('counter') – tworzy instancję testową komponentu o nazwie counter.
  • ->call('increment') – wywołuje publiczną metodę increment() w komponencie.
  • ->assertSet('count', 1) – sprawdza, czy właściwość $count w komponencie ma wartość 1 po wywołaniu metody.

Testowanie akcji komponentów

W Livewire testujemy nie tylko końcowy stan komponentu, ale również poszczególne metody i przepływy. Załóżmy, że w naszym komponencie mamy metodę submit() dokonującą jakichś operacji (np. zapis w bazie, walidacja).

Przykład testu z walidacją

Zakładamy, że w komponencie RegisterForm mamy publiczną właściwość $email, $password oraz metodę submit(), która waliduje dane.

				
					public function submit()
{
    $this->validate([
        'email' => 'required|email',
        'password' => 'required|min:8'
    ]);

    // Zapis do bazy
    // ...
}

				
			

Test, który sprawdzi błąd walidacji oraz poprawne przejście:

				
					<?php

namespace Tests\Feature;

use Tests\TestCase;
use Livewire\Livewire;

class RegisterFormTest extends TestCase
{
    /** @test */
    public function it_shows_error_if_email_is_not_valid()
    {
        Livewire::test('register-form')
            ->set('email', 'not-an-email')
            ->set('password', '12345678')
            ->call('submit')
            ->assertHasErrors(['email' => 'email']);
    }

    /** @test */
    public function it_registers_user_with_valid_data()
    {
        Livewire::test('register-form')
            ->set('email', 'test@example.com')
            ->set('password', 'secretPass123')
            ->call('submit')
            ->assertHasNoErrors()
            ->assertRedirect('/dashboard');
        // Zakładamy, że po poprawnej rejestracji przekierowujemy na /dashboard
    }
}

				
			
  • ->assertHasErrors(['email' => 'email']) – sprawdzamy, czy wystąpił błąd walidacji na polu email (konkretnie reguła email).
  • ->assertHasNoErrors() – oczekujemy, że brak błędów przy poprawnych danych.
  • ->assertRedirect('/dashboard') – jeśli w metodzie submit() następuje przekierowanie w razie sukcesu, test to sprawdzi.

Sprawdzanie tekstu/HTML renderowanego przez komponent

Możemy też sprawdzić, czy Livewire wyrenderował odpowiednie fragmenty HTML w widoku, np. czy pojawia się tekst Witaj, użytkowniku!:

				
					Livewire::test('welcome-panel')
    ->assertSee('Witaj, użytkowniku!')
    ->assertSeeHtml('<button>Pokaż profil</button>');

				
			
  • ->assertSee() – sprawdza, czy w wyrenderowanym HTML występuje podany ciąg tekstu (bez względu na otaczające tagi).
  • ->assertSeeHtml() – ściśle sprawdza, czy podany fragment HTML jest obecny w wyrenderowanym kodzie.

Możemy także sprawdzać, czy jakiś tekst nie występuje:

				
					->assertDontSee('Błąd logowania');

				
			

Organizacja testów

W standardowym podejściu Laravel:

  1. Testy jednostkowe (Unit) trafiają do folderu tests/Unit.

    • Testujemy tu raczej logikę niezależną od bazy danych czy widoku (np. metody formatujące dane, transformery).
    • Rzadziej spotkamy tam testy Livewire, bo komponent zwykle ma jednak interakcję z widokiem czy sesją.
  2. Testy funkcjonalne (Feature) trafiają do folderu tests/Feature.

    • Tutaj testujemy całe komponenty Livewire: ich metody, walidację, renderowanie widoków, komunikację z bazą itd.

Przykładowo:

				
					tests
├── Feature
│   ├── Livewire
│   │   └── CounterTest.php
│   │   └── RegisterFormTest.php
│   └── ...
└── Unit
    └── SomeUtilityTest.php

				
			

Dla testów Livewire można wydzielić osobny folder tests/Feature/Livewire, by zachować porządek.

Ćwiczenia

Ćwiczenie A: Napisać test jednostkowy (Feature) dla komponentu licznika

Załóżmy, że mamy komponent Counter, który ma prostą logikę – publiczne pole $count, metodę increment(), która zwiększa liczbę o 1, oraz metodę decrement(), która zmniejsza liczbę o 1.

1. Kod komponentu (przykładowy)

				
					<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }

    public function decrement()
    {
        $this->count--;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

				
			

2. Test: CounterTest.php

				
					<?php

namespace Tests\Feature\Livewire;

use Tests\TestCase;
use Livewire\Livewire;

class CounterTest extends TestCase
{
    /** @test */
    public function it_starts_from_zero()
    {
        Livewire::test('counter')
            ->assertSet('count', 0);
    }

    /** @test */
    public function it_increments_the_counter_by_one()
    {
        Livewire::test('counter')
            ->call('increment')
            ->assertSet('count', 1);
    }

    /** @test */
    public function it_decrements_the_counter_by_one()
    {
        Livewire::test('counter')
            ->call('decrement')
            ->assertSet('count', -1);
    }
}

				
			

W powyższym teście sprawdzamy:

  • Czy początkowo $count wynosi 0?
  • Czy wywołanie metody increment() zmienia tę wartość na 1?
  • Czy decrement() zmienia ją na -1?

Aby uruchomić test:

				
					php artisan test --filter=CounterTest

				
			

Ćwiczenie B: Testy walidacji w formularzu rejestracji

Załóżmy, że mamy komponent RegisterForm, który posiada pola: $name, $email, $password, a także metodę submit(), która waliduje dane i w razie sukcesu tworzy użytkownika w bazie.

1. Kod komponentu (przykładowy)

				
					<?php

namespace App\Http\Livewire;

use App\Models\User;
use Livewire\Component;

class RegisterForm extends Component
{
    public $name;
    public $email;
    public $password;

    protected $rules = [
        'name' => 'required|min:3',
        'email' => 'required|email',
        'password' => 'required|min:8',
    ];

    public function submit()
    {
        $this->validate();

        // Tworzymy użytkownika w bazie
        User::create([
            'name' => $this->name,
            'email' => $this->email,
            'password' => bcrypt($this->password),
        ]);

        // Przykład przekierowania
        return redirect('/dashboard');
    }

    public function render()
    {
        return view('livewire.register-form');
    }
}

				
			

2. Test: RegisterFormTest.php

				
					<?php

namespace Tests\Feature\Livewire;

use App\Models\User;
use Tests\TestCase;
use Livewire\Livewire;
use Illuminate\Foundation\Testing\RefreshDatabase;

class RegisterFormTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function name_is_required()
    {
        Livewire::test('register-form')
            ->set('name', '')
            ->set('email', 'test@example.com')
            ->set('password', 'secretPass123')
            ->call('submit')
            ->assertHasErrors(['name' => 'required']);
    }

    /** @test */
    public function email_must_be_valid()
    {
        Livewire::test('register-form')
            ->set('name', 'Jan Kowalski')
            ->set('email', 'not-a-valid-email')
            ->set('password', 'secretPass123')
            ->call('submit')
            ->assertHasErrors(['email' => 'email']);
    }

    /** @test */
    public function password_must_be_at_least_8_characters()
    {
        Livewire::test('register-form')
            ->set('name', 'Jan Kowalski')
            ->set('email', 'test@example.com')
            ->set('password', 'short')
            ->call('submit')
            ->assertHasErrors(['password' => 'min']);
    }

    /** @test */
    public function it_registers_user_with_correct_data()
    {
        Livewire::test('register-form')
            ->set('name', 'Jan Kowalski')
            ->set('email', 'test@example.com')
            ->set('password', 'secretPass123')
            ->call('submit')
            ->assertHasNoErrors()
            ->assertRedirect('/dashboard');

        // Sprawdzamy, czy użytkownik faktycznie został stworzony w bazie
        $this->assertDatabaseHas('users', [
            'name' => 'Jan Kowalski',
            'email' => 'test@example.com'
        ]);
    }
}

				
			

Wyjaśnienia:

  • ->assertHasErrors(['name' => 'required']) – sprawdzamy, czy błąd walidacji występuje dla pola name i dotyczy reguły required.
  • ->assertHasNoErrors() – brak błędów walidacji.
  • ->assertDatabaseHas('users', [...]) – weryfikujemy, czy w tabeli users pojawił się właściwy wpis.

Podsumowanie

Testowanie komponentów Livewire jest proste dzięki metodzie Livewire::test(). Można w ten sposób:

  • Ustawiać wartości właściwości (->set('name', 'Jan')).
  • Wywoływać metody komponentu (->call('submit')).
  • Sprawdzać komunikaty o błędach walidacyjnych (->assertHasErrors()).
  • Weryfikować renderowany tekst/HTML (->assertSee() i ->assertDontSee()).
  • Kontrolować, czy wystąpiło przekierowanie (->assertRedirect()).

Dzięki temu testy stają się źródłem bezpieczeństwa i pewności, że nasze komponenty Livewire – w tym logika walidacji, CRUD, czy mechanizmy uploadu – będą działały zgodnie z oczekiwaniami.

Proponowane rozwiązania ćwiczeń

  1. Test jednostkowy dla komponentu licznika:
    • W sekcji Ćwiczenie A znajdziesz gotowy plik testowy CounterTest.php, który testuje metody increment() i decrement().
  2. Testy dla formularza rejestracji:
    • W sekcji Ćwiczenie B pokazaliśmy, jak sprawdzić, czy formularz reaguje poprawnie na niepoprawne dane (błędy walidacji) oraz czy tworzy rekord w bazie przy danych poprawnych.

To dopiero początek – w praktyce możesz także testować emisję eventów (->assertEmitted('nazwaZdarzenia')), reakcje w wielu komponentach czy nawet interakcje z bazą i plikami (w połączeniu z mockowaniem dysku). Dzięki takiemu podejściu Twoje aplikacje Livewire będą nie tylko bogate w funkcjonalności, ale i niezawodne w codziennym użytkowaniu.