# Laravel Testing w 2026: Pest, Mocking i pytania na rozmowę techniczną > Kompleksowy przewodnik po testowaniu aplikacji Laravel z wykorzystaniem Pest, mockowania fasad i Mockery. Artykuł zawiera praktyczne przykłady kodu oraz pytania techniczne przygotowujące do rozmów rekrutacyjnych. - Published: 2026-06-04 - Updated: 2026-06-04 - Author: SharpSkill - Tags: laravel, testing, pest, php, interview - Reading time: 8 min --- Testowanie aplikacji Laravel ewoluowało znacząco na przestrzeni ostatnich lat. W 2026 roku framework Pest stał się de facto standardem w ekosystemie PHP, oferując ekspresyjną składnię i zaawansowane funkcje, które znacznie przewyższają tradycyjne podejście PHPUnit. Dla programistów przygotowujących się do rozmów technicznych, solidna znajomość strategii testowania stanowi kluczowy element wyróżniający kandydatów na tle konkurencji. Współczesne aplikacje Laravel wymagają wielowarstwowego podejścia do testowania. Testy jednostkowe weryfikują izolowaną logikę biznesową, testy funkcjonalne sprawdzają integrację komponentów, a testy architektoniczne wymuszają przestrzeganie konwencji projektowych. Dodatkowo, mutation testing pozwala ocenić rzeczywistą jakość zestawu testów, wykraczając poza tradycyjne metryki pokrycia kodu. Niniejszy artykuł przedstawia kompleksowe podejście do testowania Laravel, obejmując konfigurację Pest, mockowanie fasad i usług zewnętrznych, testy architektoniczne oraz praktyczne scenariusze spotykane podczas rozmów rekrutacyjnych. > **Dlaczego Pest zamiast PHPUnit?** > > Pest oferuje znacznie bardziej czytelną składnię dzięki funkcjom wyższego rzędu i łańcuchowym asercjom expect(). Framework automatycznie integruje się z Laravel, eliminując boilerplate code typowy dla PHPUnit. Dodatkowo, Pest wprowadza natywne wsparcie dla testów architektonicznych i mutation testing, co czyni go kompletnym rozwiązaniem dla nowoczesnych projektów PHP. ## Konfiguracja Pest w projekcie Laravel Prawidłowa konfiguracja Pest stanowi fundament efektywnego testowania. Plik `tests/Pest.php` definiuje globalne ustawienia, w tym automatyczne odświeżanie bazy danych dla testów funkcjonalnych. ```php // tests/Pest.php use Illuminate\Foundation\Testing\RefreshDatabase; pest() ->extend(Tests\TestCase::class) ->use(RefreshDatabase::class) ->in('Feature'); ``` Powyższa konfiguracja zapewnia, że wszystkie testy w katalogu `Feature` automatycznie dziedziczą po klasie `TestCase` Laravel oraz korzystają z traita `RefreshDatabase`. Dzięki temu każdy test rozpoczyna się z czystą bazą danych, eliminując problemy z zależnościami między testami. Dyrektywa `->in('Feature')` ogranicza zastosowanie tej konfiguracji do testów funkcjonalnych. Testy jednostkowe umieszczone w `tests/Unit/` dziedziczą lżejsze środowisko, bez połączenia z bazą danych, co przyspiesza ich wykonanie. Ten podział architektoniczny między testami jednostkowymi a funkcjonalnymi odzwierciedla klasyczną piramidę testów. ## Testy funkcjonalne: walidacja endpointów HTTP Testy funkcjonalne weryfikują poprawność działania całych przepływów aplikacji, od żądania HTTP po odpowiedź serwera. W kontekście API REST, testy te sprawdzają status odpowiedzi, strukturę JSON oraz efekty uboczne w bazie danych. ```php // tests/Feature/UserRegistrationTest.php use App\Models\User; 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(); }); ``` Test powyżej demonstruje typowy scenariusz rejestracji użytkownika. Weryfikuje zarówno odpowiedź HTTP (status 201, struktura JSON), jak i rzeczywisty stan bazy danych. Kombinacja asercji Laravel z ekspresyjną składnią `expect()` Pest tworzy czytelny i kompletny test. Metoda `postJson()` wysyła żądanie POST z automatycznie skonfigurowanymi nagłówkami JSON, symulując rzeczywistego klienta API. Asercja `assertJsonStructure` waliduje obecność oczekiwanych kluczy bez narzucania konkretnych wartości, co czyni test odpornym na drobne zmiany w odpowiedzi. ## Testy jednostkowe z Pest: izolowana logika biznesowa Testy jednostkowe koncentrują się na pojedynczych klasach lub metodach, izolując je od zewnętrznych zależności. Funkcja `describe()` w Pest umożliwia grupowanie powiązanych testów. ```php // tests/Unit/PriceCalculatorTest.php use App\Services\PriceCalculator; 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); }); }); ``` Blok `describe()` grupuje testy dotyczące klasy `PriceCalculator`, poprawiając organizację i czytelność wyników. Asercja `toThrow()` weryfikuje poprawność obsługi błędów, co stanowi istotny element testowania defensywnego. Testy jednostkowe wykonują się w milisekundach i stanowią najbardziej niezawodną podstawę piramidy testów. Podczas rozmowy technicznej umiejętność rozróżnienia między tym, co podlega testom jednostkowym (logika obliczeń, transformacje danych, walidacja reguł biznesowych), a tym, co podlega testom funkcjonalnym (interakcja HTTP, integracja z bazą danych) stanowi kluczowy wskaźnik dojrzałości technicznej. ## Mockowanie fasad Laravel: Mail, Queue, Notifications Fasady Laravel oferują wbudowane mechanizmy mockowania poprzez metodę `fake()`. Podejście to pozwala testować kod wywołujący usługi zewnętrzne bez rzeczywistego wysyłania e-maili czy dodawania zadań do kolejki. ```php // tests/Feature/OrderProcessingTest.php use App\Models\Order; use App\Models\User; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use App\Mail\OrderConfirmation; use App\Jobs\ProcessPayment; 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); }); }); ``` Test weryfikuje, że potwierdzenie zamówienia uruchamia odpowiednie efekty uboczne: zadanie przetwarzania płatności trafia do kolejki, a e-mail potwierdzający jest wysyłany do klienta. Callbacki w asercjach pozwalają sprawdzić szczegółowe właściwości dispatchowanych obiektów. Wywołanie `Mail::fake()` i `Queue::fake()` przechwytuje odpowiednio wysyłkę e-maili i dispatch jobów na czas trwania testu. Żaden rzeczywisty e-mail nie zostaje wysłany, żaden job nie jest wykonywany w tle. Asercje `assertPushed` i `assertSent` weryfikują następnie, że odpowiednie klasy zostały zdispatchowane z oczekiwanymi parametrami. ## Zaawansowane mockowanie z Mockery: usługi zewnętrzne Dla usług nieposiadających wbudowanego mechanizmu fake, biblioteka Mockery zapewnia pełną kontrolę nad zachowaniem mocków. Podejście to sprawdza się szczególnie przy integracji z zewnętrznymi API. ```php // tests/Feature/PaymentGatewayTest.php use App\Services\PaymentGateway; use App\Services\StripeClient; 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'); }); ``` Mock definiuje oczekiwane wywołanie metody `charge()` z konkretnymi argumentami i zwraca predefiniowaną odpowiedź. Binding mocka w kontenerze IoC Laravel sprawia, że wstrzykiwanie zależności automatycznie wykorzystuje testowy double zamiast rzeczywistego klienta Stripe. Łańcuch `shouldReceive()->once()->with()->andReturn()` definiuje ścisły kontrakt: metoda `charge` musi zostać wywołana dokładnie raz, z określonymi parametrami, i zwróci zdefiniowaną wartość. Każde odchylenie powoduje niepowodzenie testu. Mechanizm ten opiera się na zasadzie odwrócenia zależności i stanowi fundament testowalności w dobrze zarchitekturyzowanych aplikacjach Laravel. ## Testy architektoniczne: wymuszanie konwencji projektowych Pest wprowadza natywne wsparcie dla testów architektonicznych, umożliwiając automatyczną weryfikację struktury kodu. Testy te zapobiegają naruszeniom wzorców projektowych na etapie CI/CD. ```php // tests/Architecture/ArchitectureTest.php arch('controllers do not use Eloquent directly') ->expect('App\Http\Controllers') ->not->toUse('Illuminate\Database\Eloquent'); arch('services are final classes') ->expect('App\Services') ->toBeFinal(); arch('no debugging functions in production code') ->expect(['dd', 'dump', 'var_dump', 'ray']) ->not->toBeUsed(); ``` Powyższe reguły wymuszają separację warstw (kontrolery nie komunikują się bezpośrednio z Eloquent), niezmienność klas serwisowych oraz brak funkcji debugowania w kodzie produkcyjnym. Pierwszy test wymusza ścisłą separację architektoniczną: kontrolery nie mogą bezpośrednio manipulować Eloquent, wymuszając użycie warstwy serwisów lub repozytoriów. To ograniczenie zapobiega gromadzeniu logiki biznesowej w kontrolerach. Drugi test gwarantuje, że klasy serwisów są zadeklarowane jako `final`, uniemożliwiając niekontrolowane dziedziczenie komplikujące utrzymanie kodu. Pest oferuje również gotowe presety dla typowych scenariuszy. ```php // tests/Architecture/LaravelPresetTest.php arch()->preset()->laravel(); arch()->preset()->security(); arch()->preset()->php(); ``` Preset `laravel()` weryfikuje, czy kontrolery rozszerzają właściwą klasę bazową, czy modele znajdują się we właściwym namespace i czy konwencje nazewnicze są przestrzegane. Preset `security()` wykrywa użycie niebezpiecznych funkcji takich jak `eval()`, `exec()` czy `shell_exec()`. Preset `php()` wymusza ogólne dobre praktyki języka. Znajomość testów architektonicznych wyróżnia na rozmowach kwalifikacyjnych kandydatów posiadających globalną wizję jakości oprogramowania od tych, którzy ograniczają się do testów funkcjonalnych. ## Mutation testing: ocena rzeczywistej jakości testów Pokrycie kodu (code coverage) nie gwarantuje skuteczności testów. Mutation testing wprowadza celowe modyfikacje do kodu źródłowego i sprawdza, czy testy wykrywają te zmiany. ```bash # Run mutation testing on a specific class php artisan test --mutate --class=App\\Services\\PriceCalculator ``` Wysoki mutation score (procent zabitych mutantów) wskazuje na testy skutecznie weryfikujące logikę biznesową. Porównanie poniższych testów ilustruje różnicę w jakości. ```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 gdy funkcja zwraca całkowicie błędną wartość. Drugi test z precyzyjną asercją wykrywa mutacje operatorów arytmetycznych i stałych wartości. Mutation testing ujawnia obszary kodu, w których pokrycie ilościowe maskuje słabości jakościowe. Wynik mutacji na poziomie 90% lub wyższym wskazuje na suite testową rzeczywiście niezawodną, zdolną wykryć subtelne regresje, które pokrycie linii na poziomie 100% mogłoby przeoczyć. ## Kompletne scenariusze testowe na rozmowy rekrutacyjne Rozmowy techniczne często wymagają implementacji testów dla złożonych przepływów biznesowych. Poniższy przykład demonstruje testowanie publikacji artykułu z weryfikacją stanu i znacznika czasu. ```php // tests/Feature/ArticlePublishingTest.php use App\Models\Article; use App\Models\User; 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(); }); ``` Test weryfikuje pełny przepływ: autoryzację użytkownika z odpowiednią rolą, zmianę statusu artykułu oraz ustawienie znacznika czasu publikacji. Łańcuchowe asercje `expect()` zwiększają czytelność weryfikacji. Fabryka `draft()` tworzy artykuł w stanie roboczym, symulując kontekst początkowy. Asercja `assertJsonPath` weryfikuje konkretną wartość w odpowiedzi JSON bez narzucania całej struktury. Wywołanie `refresh()` przeładowuje model z bazy danych, gwarantując, że asercje dotyczą stanu utrwalonego, a nie lokalnego cache. Kolejny scenariusz obejmuje testy uwierzytelniania API, typowe dla aplikacji SPA komunikujących się z backendem Laravel. ```php // tests/Feature/ApiAuthenticationTest.php use App\Models\User; 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']); }); }); ``` Grupa testów `describe()` obejmuje trzy kluczowe scenariusze: ochronę endpointów przed nieautoryzowanym dostępem, poprawne zwracanie profilu zalogowanego użytkownika oraz walidację danych wejściowych przy rejestracji. Blok `describe` grupuje powiązane scenariusze pod wspólną etykietą, co ułatwia identyfikację zakresu testów w raporcie wykonania. Każda funkcja `it()` stanowi niezależny przypadek testowy z pełną izolacją stanu. ## Pytania techniczne o testowanie Laravel na rozmowach rekrutacyjnych Poniżej przedstawiono najczęściej zadawane pytania dotyczące testowania w ekosystemie Laravel wraz z kluczowymi punktami odpowiedzi. **Czym różni się test jednostkowy od funkcjonalnego w Laravel?** Testy jednostkowe weryfikują pojedyncze klasy lub metody w izolacji, bez uruchamiania frameworka. Testy funkcjonalne wykorzystują pełny kontekst Laravel, w tym routing, middleware, bazę danych i kontener IoC. W praktyce testy jednostkowe są szybsze, ale testy funkcjonalne lepiej odzwierciedlają rzeczywiste zachowanie aplikacji. **Jak działa trait RefreshDatabase i kiedy go używać?** Trait `RefreshDatabase` uruchamia migracje przed pierwszym testem, a następnie opakowuje każdy kolejny test w transakcję, którą wycofuje po zakończeniu. Podejście to jest szybsze niż `DatabaseMigrations` (które uruchamia migracje przed każdym testem) i zapewnia izolację bez nadmiernego obciążenia. **Wyjaśnij różnicę między Mail::fake() a Mockery::mock() dla usługi mailowej.** Metoda `Mail::fake()` podmienia implementację mailera na testowy double wbudowany w Laravel, oferując dedykowane asercje jak `assertSent()` czy `assertQueued()`. Mockery wymaga ręcznego definiowania oczekiwań i zwracanych wartości, ale oferuje większą elastyczność dla niestandardowych scenariuszy. Dla standardowych przypadków `Mail::fake()` jest preferowanym rozwiązaniem. **Co to jest mutation testing i jak poprawia jakość testów?** Mutation testing automatycznie wprowadza drobne zmiany (mutacje) do kodu źródłowego, np. zamienia `>` na `>=` lub `true` na `false`. Następnie uruchamia testy sprawdzając, czy wykrywają wprowadzone błędy. Wysoki mutation score wskazuje, że testy skutecznie weryfikują logikę, a nie tylko zwiększają metrykę pokrycia. **Jak testować zdarzenia i listenery w Laravel?** Metoda `Event::fake()` przechwytuje dispatchowane zdarzenia, umożliwiając weryfikację za pomocą `assertDispatched()`. Dla testowania samych listenerów można je wywoływać bezpośrednio z instancją zdarzenia, traktując je jak zwykłe klasy. Podejście hybrydowe testuje przepływ end-to-end z fake'owanymi efektami ubocznymi. **Jakie są najlepsze praktyki organizacji testów w dużych projektach Laravel?** Rekomendowana struktura obejmuje podział na katalogi `Unit`, `Feature` i `Architecture`. Testy funkcjonalne powinny odzwierciedlać strukturę routingu lub domen biznesowych. Wykorzystanie helperów i traitów redukuje duplikację kodu. Testy architektoniczne wymuszają konwencje na poziomie CI/CD. **Kiedy stosować Mockery zamiast faków fasad?** Faki fasad (`Mail::fake()`, `Queue::fake()`) wystarczą dla usług wbudowanych w Laravel. Mockery jest niezbędne dla usług zewnętrznych (klienty API, SDK podmiotów trzecich), które nie są fasadami. Ogólna zasada: używać faków Laravel tam, gdzie istnieją, Mockery do reszty. **Co walidują testy architektury, czego nie pokrywają testy klasyczne?** Testy architektury weryfikują strukturę i zależności kodu bez jego wykonywania. Wykrywają naruszenia konwencji (kontroler używający bezpośrednio Eloquent, zapomniana funkcja debugowania, serwis niezadeklarowany jako final), których testy funkcjonalne nie mogą wychwycić, ponieważ kod działa poprawnie mimo naruszenia architektonicznego. ## Podsumowanie Testowanie aplikacji Laravel w 2026 roku wymaga znajomości wielu technik i narzędzi. Pest oferuje ekspresyjną składnię i zaawansowane funkcje, które znacznie upraszczają pisanie i utrzymywanie testów. Mockowanie fasad Laravel i wykorzystanie Mockery dla usług zewnętrznych pozwala testować złożone przepływy bez zależności od infrastruktury. Testy architektoniczne stanowią nowość w ekosystemie PHP, umożliwiając automatyczną weryfikację struktury kodu. Mutation testing wykracza poza tradycyjne metryki pokrycia, oceniając rzeczywistą skuteczność zestawu testów. Dla programistów przygotowujących się do rozmów technicznych, praktyczna znajomość tych koncepcji oraz umiejętność implementacji kompletnych scenariuszy testowych stanowi istotną przewagę konkurencyjną. Kluczowe zagadnienia na potrzeby rozmów technicznych obejmują: konfigurację Pest z podziałem na testy jednostkowe i funkcjonalne, kombinację asercji HTTP i expect() dla testów funkcjonalnych, mockowanie fasad dla procesów asynchronicznych, wykorzystanie Mockery dla usług zewnętrznych z wstrzykiwaniem przez kontener, testy architektoniczne kodyfikujące decyzje strukturalne zespołu, oraz mutation testing ujawniający rzeczywistą jakość asercji. Kluczem do sukcesu jest systematyczne stosowanie wielowarstwowego podejścia: testy jednostkowe dla izolowanej logiki, testy funkcjonalne dla integracji komponentów, testy architektoniczne dla konwencji projektowych oraz mutation testing dla weryfikacji jakości całego zestawu testów. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/laravel/laravel-testing-pest-mocking-best-practices