Testowanie Laravel z Pest 5 w 2026: TIA, Mocking i pytania rekrutacyjne

Opanuj najlepsze praktyki testowania Laravel z Pest 5, Test Impact Analysis, Mockery, facade fakes i testami architektonicznymi. Obejmuje testy jednostkowe, testy funkcjonalne, strategie mockowania i typowe pytania rekrutacyjne dla programistów Laravel.

Testowanie Laravel z Pest - mockowanie fasad i pytania rekrutacyjne w 2026

Najlepsze praktyki testowania Laravel uległy znacznym zmianom wraz z Pest 5, silnikiem TIA (Test Impact Analysis) oraz PHPUnit 13. Laravel 13 oferuje natywne wsparcie dla Pest, zapewniając bardziej ekspresywne i mniej rozwlekłe pisanie testów niż w przypadku czystego PHPUnit. Ten przewodnik obejmuje wzorce istotne zarówno dla aplikacji produkcyjnych, jak i rozmów technicznych.

Pest 5 jako standard

Pest 5 (zbudowany na PHPUnit 13) jest domyślnym frameworkiem testowym dla Laravel 13. Wymaga PHP 8.4+ i wprowadza silnik TIA, natywną weryfikację agentów AI, integrację z PHPStan oraz time-balanced sharding.

Konfiguracja Pest 5 w projekcie Laravel 13

Każda nowa aplikacja Laravel 13 automatycznie konfiguruje Pest. Dla istniejących projektów migracja z Pest 4 zajmuje kilka minut, ponieważ główną kwestią jest kompatybilność z PHPUnit 13. Konfiguracja znajduje się w pliku tests/Pest.php, gdzie rejestrowane są globalne traity i metody pomocnicze.

tests/Pest.phpphp
use IlluminateFoundationTestingRefreshDatabase;

pest()
    ->extend(TestsTestCase::class)
    ->use(RefreshDatabase::class)
    ->in('Feature');

Ten pojedynczy plik zastępuje dawny trait CreatesApplication oraz dziedziczenie po bazowej klasie testowej. Trait RefreshDatabase opakowuje każdy test w transakcję bazodanową, automatycznie wycofując zmiany.

tests/Feature/UserRegistrationTest.phpphp
use AppModelsUser;

it('registers a new user with valid data', function () {
    $response = $this->postJson('/api/register', [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => 'SecurePass123!',
        'password_confirmation' => 'SecurePass123!',
    ]);

    $response->assertStatus(201)
        ->assertJsonStructure(['user' => ['id', 'name', 'email']]);

    expect(User::where('email', 'jane@example.com')->exists())->toBeTrue();
});

API expect() w Pest łączy się naturalnie z asercjami PHPUnit. Wywołanie assertJsonStructure waliduje strukturę odpowiedzi, podczas gdy expect()->toBeTrue() potwierdza stan bazy danych. Oba style współistnieją bez konfliktów.

Test Impact Analysis: flagowa funkcja Pest 5

Silnik TIA sprawia, że Pest 5 jest przełomowy dla dużych baz kodu. Podczas pierwszego uruchomienia Pest rejestruje, które testy dotykają których plików. Każde kolejne uruchomienie wykonuje tylko testy dotknięte zmianami i odtwarza cached wyniki dla pozostałych.

bash
# First run: records baseline (requires PCOV or Xdebug)
php artisan test

# Subsequent runs: only affected tests execute
php artisan test

Suite testowy Laravel Cloud, zawierający ponad 19 000 testów, skrócił czas wykonania z około trzech minut do pięciu sekund dzięki TIA. Silnik rozumie więcej niż pliki PHP: wykrywa zmiany w migracjach, szablonach Blade i współdzielonych komponentach JavaScript.

TIA wymaga sterownika pokrycia (PCOV lub Xdebug) do nagrania baseline. W pipeline'ach CI cached mapowania są zachowywane między uruchomieniami, co sprawia, że zysk wydajnościowy kumuluje się.

Testy jednostkowe vs funkcjonalne w Laravel

Rozróżnienie między testami jednostkowymi a funkcjonalnymi w Laravel określa, które części frameworka są uruchamiane. Testy jednostkowe działają bez kontenera aplikacji, co czyni je szybszymi, ale ograniczonymi do czystej logiki. Testy funkcjonalne uruchamiają pełną aplikację, umożliwiając wywołania HTTP, zapytania do bazy danych i rozwiązywanie serwisów.

tests/Unit/PriceCalculatorTest.phpphp
use AppServicesPriceCalculator;

describe('PriceCalculator', function () {
    it('applies a percentage discount correctly', function () {
        $calculator = new PriceCalculator();

        // 20% off a 150.00 base price
        $result = $calculator->applyDiscount(150.00, 20);

        expect($result)->toBe(120.00);
    });

    it('rejects negative discount values', function () {
        $calculator = new PriceCalculator();

        expect(fn () => $calculator->applyDiscount(100.00, -5))
            ->toThrow(InvalidArgumentException::class);
    });
});

Testy jednostkowe celują w izolowane klasy bez zewnętrznych zależności. Blok describe grupuje powiązane asercje, a Pest 5 obsługuje zagnieżdżone bloki describe dla złożonych hierarchii testów.

Testy funkcjonalne powinny stanowić większość zestawu testów Laravel. Wychwytują błędy integracyjne, których testy jednostkowe nie wykrywają, takie jak nieprawidłowe middleware routingu, brakujące reguły walidacji czy uszkodzone relacje Eloquent. Ogólna zasada: jeśli kod dotyka bazy danych, warstwy HTTP lub fasady, należy napisać test funkcjonalny.

Strategie mockowania z fasadami i Mockery

Mockowanie w testach Laravel izoluje testowany kod od zewnętrznych zależności. Fasady Laravel zapewniają wbudowane fake'owe implementacje dla kolejek, zdarzeń, powiadomień, maili i storage. Mockery obsługuje wszystko inne.

tests/Feature/OrderProcessingTest.phpphp
use AppModelsOrder;
use AppModelsUser;
use IlluminateSupportFacadesMail;
use IlluminateSupportFacadesQueue;
use AppMailOrderConfirmation;
use AppJobsProcessPayment;

it('dispatches payment job and sends confirmation email', function () {
    // Fake both Mail and Queue facades
    Mail::fake();
    Queue::fake();

    $user = User::factory()->create();
    $order = Order::factory()->for($user)->create([
        'total' => 99.99,
        'status' => 'pending',
    ]);

    // Act: confirm the order via HTTP
    $this->actingAs($user)
        ->postJson("/api/orders/{$order->id}/confirm")
        ->assertOk();

    // Assert: payment job was dispatched with correct amount
    Queue::assertPushed(ProcessPayment::class, function ($job) use ($order) {
        return $job->order->id === $order->id
            && $job->order->total === 99.99;
    });

    // Assert: confirmation email was sent to the user
    Mail::assertSent(OrderConfirmation::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
});

Facade fakes przechwytują wywołania na poziomie frameworka, zapobiegając rzeczywistemu wysyłaniu emaili czy dispatchowaniu jobów. Asercje oparte na closures weryfikują dokładne dane przekazywane do każdego komponentu.

Mockuj na granicach

Unikaj mockowania wewnętrznych klas domenowych. Mockuj na granicach: zewnętrzne API, mail, kolejki, systemy plików. Nadmierne mockowanie sprawia, że testy są kruche i ściśle powiązane ze szczegółami implementacji.

Dla zależności niebędących fasadami, wstrzykuj je przez konstruktor i używaj Mockery:

tests/Feature/PaymentGatewayTest.phpphp
use AppServicesPaymentGateway;
use AppServicesStripeClient;

it('charges the customer through the payment gateway', function () {
    // Create a mock of the Stripe client
    $stripeClient = Mockery::mock(StripeClient::class);
    $stripeClient->shouldReceive('charge')
        ->once()
        ->with('cus_abc123', 5000, 'usd')
        ->andReturn(['status' => 'succeeded', 'id' => 'ch_xyz']);

    // Bind the mock in the container
    $this->app->instance(StripeClient::class, $stripeClient);

    $gateway = app(PaymentGateway::class);
    $result = $gateway->processCharge('cus_abc123', 50.00);

    expect($result['status'])->toBe('succeeded');
});

Bindowanie mocka przez $this->app->instance() zastępuje rzeczywistą implementację na czas trwania testu. Mockery weryfikuje, że charge zostało wywołane dokładnie raz z oczekiwanymi argumentami.

Testy architektoniczne z presetami Pest

Pest 5 zawiera testy architektoniczne, które wymuszają reguły strukturalne w całej bazie kodu. Testy te działają na AST, nie w runtime, co czyni je niezwykle szybkimi.

tests/Architecture/ArchitectureTest.phpphp
arch('controllers do not use Eloquent directly')
    ->expect('AppHttpControllers')
    ->not->toUse('IlluminateDatabaseEloquent');

arch('services are final classes')
    ->expect('AppServices')
    ->toBeFinal();

arch('no debugging functions in production code')
    ->expect(['dd', 'dump', 'var_dump', 'ray'])
    ->not->toBeUsed();

Pierwsza reguła zapobiega bezpośredniemu odpytywaniu bazy danych przez kontrolery, wymuszając wzorzec warstwy serwisów. Druga zapewnia, że serwisy nie mogą być rozszerzane, redukując złożoność dziedziczenia. Trzecia wychwytuje pozostawione instrukcje debugowania przed dotarciem do produkcji.

Dostępne są również presety specyficzne dla Laravel:

tests/Architecture/LaravelPresetTest.phpphp
arch()->preset()->laravel();
arch()->preset()->security();
arch()->preset()->php();

Te trzy linie wymuszają dziesiątki reguł: modele rozszerzają poprawną klasę bazową, kontrolery nie zawierają logiki biznesowej, brak niebezpiecznych funkcji jak eval() czy md5() do hashowania, oraz przestrzegane są standardowe konwencje PHP.

Gotowy na rozmowy o Laravel?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Mutation testing do weryfikacji jakości testów

Pokrycie kodu mierzy, które linie są wykonywane podczas testów. Mutation testing idzie dalej: modyfikuje kod źródłowy i sprawdza, czy testy wykrywają zmianę. Jeśli mutacja przeżyje, zestaw testów ma lukę.

bash
# Run mutation testing on a specific class
php artisan test --mutate --class=App\Services\PriceCalculator

Pest 5 wprowadza mutacje takie jak zmiana > na >=, usuwanie instrukcji return i odwracanie warunków boolowskich. Wynik mutacji poniżej 80% zwykle wskazuje, że testy weryfikują wyniki bez sprawdzania przypadków brzegowych.

php
// Example: this test has high coverage but low mutation score
it('calculates shipping cost', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Only checks that the result is numeric
    expect($cost)->toBeFloat();
});

// Improved: catches mutations by asserting the exact value
it('calculates domestic shipping for 5kg package', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Exact assertion catches operator and value mutations
    expect($cost)->toBe(12.50);
});

Pierwszy test przechodzi nawet jeśli logika obliczeniowa jest całkowicie błędna, o ile zwraca float. Drugi test natychmiast pada, gdy mutacja zmienia formułę. Precyzyjne asercje dają wyższe wyniki mutacji.

Wzorce testowania bazy danych i fabryki

Fabryki modeli Laravel generują realistyczne dane testowe bez ręcznego konstruowania tablic. Pest 5 w połączeniu z fabrykami daje czytelną, łatwą w utrzymaniu konfigurację danych.

tests/Feature/ArticlePublishingTest.phpphp
use AppModelsArticle;
use AppModelsUser;

it('publishes a draft article and updates the timestamp', function () {
    $author = User::factory()->create(['role' => 'editor']);

    $article = Article::factory()
        ->for($author, 'author')
        ->draft()
        ->create(['title' => 'Testing Best Practices']);

    $this->actingAs($author)
        ->patchJson("/api/articles/{$article->id}/publish")
        ->assertOk()
        ->assertJsonPath('data.status', 'published');

    $article->refresh();

    expect($article->status)->toBe('published')
        ->and($article->published_at)->not->toBeNull()
        ->and($article->published_at->isToday())->toBeTrue();
});

Stan fabryki draft() ustawia domyślne wartości dla nieopublikowanych artykułów. Łańcuchowe asercje expect() z ->and() czytają się jak naturalny język i wyświetlają opisowe komunikaty w przypadku niepowodzenia.

Testowanie równoległe i sharding

Uruchom php artisan test --parallel aby wykonać testy w wielu procesach. Laravel automatycznie tworzy oddzielne testowe bazy danych dla każdego procesu, zapobiegając konfliktom danych. Time-balanced sharding w Pest 5 dystrybuuje testy według rzeczywistego czasu wykonania zamiast liczby, zapewniając, że każdy shard CI kończy się w tym samym momencie.

Typowe pytania rekrutacyjne o testowanie Laravel

Rozmowy techniczne na stanowiska Laravel często badają wiedzę o testowaniu. Oto pytania, które pojawiają się najczęściej, wraz z kluczowymi punktami odpowiedzi.

Jaka jest różnica między fake(), mock() i spy() w testach Laravel?

Fasadowe fake() zastępuje całą fasadę implementacją in-memory (Mail::fake, Queue::fake). mock() przez Mockery ustawia oczekiwania przed wykonaniem i pada jeśli nie są spełnione. spy() rejestruje interakcje i pozwala na asercje po wykonaniu, bez ustawiania oczekiwań z góry. Wybór zależy od tego, czy test musi weryfikować zachowanie (mock), rejestrować interakcje (spy), czy zapobiegać efektom ubocznym (fake).

Czym różni się RefreshDatabase od DatabaseTransactions?

RefreshDatabase uruchamia migracje raz i opakowuje każdy test w transakcję. DatabaseTransactions zakłada, że baza ma już poprawny schemat i tylko opakowuje testy w transakcje. RefreshDatabase jest bezpieczniejszy dla pipeline'ów CI, gdzie baza może jeszcze nie istnieć. DatabaseTransactions jest szybszy, gdy schemat jest gwarantowany jako aktualny.

Kiedy preferować testy funkcjonalne nad jednostkowymi?

Testy funkcjonalne powinny pokrywać każdy kod wchodzący w interakcję z warstwą HTTP, bazą danych lub serwisami Laravel. Testy jednostkowe są zarezerwowane dla czystych funkcji i obiektów wartości bez zależności od frameworka. W typowej aplikacji Laravel testy funkcjonalne przewyższają liczebnie testy jednostkowe w stosunku około 3:1 lub wyższym. Odzwierciedla to rzeczywistość, że większość kodu Laravel jest z natury zintegrowana z frameworkiem.

Jak Test Impact Analysis poprawia wydajność CI?

TIA rejestruje, które testy dotykają których plików źródłowych podczas pierwszego uruchomienia. Przy kolejnych uruchomieniach Pest wykonuje tylko testy dotknięte zmienionymi plikami i odtwarza cached wyniki dla niezmiennych testów. Duże zestawy, które zajmowały minuty, mogą zakończyć się w sekundach. Kompromis: TIA wymaga sterownika pokrycia (PCOV lub Xdebug) do zbudowania mapy zależności, a pierwsze uruchomienie trwa dłużej niż normalne.

Jak mutation testing poprawia jakość testów poza pokryciem kodu?

Pokrycie kodu mierzy ścieżki wykonania. Test może osiągnąć 100% pokrycia wywołując każdą metodę bez asercji czegokolwiek znaczącego. Mutation testing modyfikuje kod źródłowy (zmieniając operatory, usuwając return, odwracając booleany) i weryfikuje, że przynajmniej jeden test pada. Przetrwałe mutacje ujawniają zbyt luźne lub brakujące asercje. Uruchomienie php artisan test --mutate daje procentowy wynik mutacji obok standardowego pokrycia.

Więcej pytań rekrutacyjnych Laravel można znaleźć w bazie pytań SharpSkill obejmującej uwierzytelnianie, wzorce kontenera serwisów, relacje Eloquent i architekturę kolejek.

Testowanie odpowiedzi HTTP i asercje JSON

Helpery testów HTTP Laravel weryfikują kody statusu, nagłówki, struktury JSON i cele przekierowań. W połączeniu z Pest tworzą zwięzłe testy integracyjne.

tests/Feature/ApiAuthenticationTest.phpphp
use AppModelsUser;

describe('API Authentication', function () {
    it('rejects unauthenticated requests with 401', function () {
        $this->getJson('/api/profile')
            ->assertUnauthorized();
    });

    it('returns the authenticated user profile', function () {
        $user = User::factory()->create([
            'name' => 'John Doe',
            'email' => 'john@example.com',
        ]);

        $this->actingAs($user)
            ->getJson('/api/profile')
            ->assertOk()
            ->assertJson([
                'data' => [
                    'name' => 'John Doe',
                    'email' => 'john@example.com',
                ],
            ]);
    });

    it('validates required fields on registration', function () {
        $this->postJson('/api/register', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['name', 'email', 'password']);
    });
});

Blok describe grupuje testy związane z uwierzytelnianiem. Każda nazwa testu opisuje oczekiwane zachowanie, nie implementację. Metoda assertJsonValidationErrors sprawdza, czy konkretne pola mają komunikaty błędów walidacji.

Źródła

Kluczowe wnioski o testowaniu Laravel z Pest 5

  • Pest 5 z Laravel 13 eliminuje boilerplate dzięki płynnemu API konfiguracji, łańcuchom expect() i automatycznemu odkrywaniu testów
  • Silnik TIA uruchamia ponownie tylko dotknięte testy, skracając wykonanie dużych zestawów z minut do sekund bez utraty dokładności pokrycia
  • Testy funkcjonalne powinny stanowić rdzeń zestawu testów Laravel, z testami jednostkowymi zarezerwowanymi dla izolowanej logiki biznesowej
  • Facade fakes (Mail::fake(), Queue::fake()) obsługują mockowanie na poziomie frameworka, podczas gdy Mockery obsługuje zależności zewnętrzne wstrzykiwane przez kontener
  • Testy architektoniczne wymuszają reguły strukturalne (brak Eloquent w kontrolerach, brak funkcji debug) bez narzutu runtime
  • Mutation testing z --mutate wychwytuje słabe asercje, których samo pokrycie kodu nie wykrywa
  • Stany fabryk i łańcuchowe asercje expect()->and() utrzymują konfigurację danych testowych czytelną, a asercje precyzyjne
  • Do przygotowania do rozmów o Laravel, zrozumienie mock/fake/spy, zachowania RefreshDatabase, TIA i mutation testing demonstruje dojrzałość testową wykraczającą poza podstawowe pokrycie

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Wyzwanie dnia

Znajdziesz błąd w Laravel?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 2 września 2026

Tagi

#laravel
#testing
#pest
#php
#interview

Udostępnij

Powiązane artykuły