Тестування Laravel з Pest 5 у 2026: TIA, Mocking та питання співбесід

Опануйте найкращі практики тестування Laravel з Pest 5, Test Impact Analysis, Mockery, facade fakes та архітектурними тестами. Охоплює unit-тести, feature-тести, стратегії мокінгу та типові питання співбесід для Laravel-розробників.

Тестування Laravel з Pest: мокінг, архітектурні тести та підготовка до співбесід

Найкращі практики тестування Laravel суттєво змінилися з появою Pest 5, рушія TIA (Test Impact Analysis) та PHPUnit 13. Laravel 13 має першокласну підтримку Pest, що робить процес тестування більш виразним і менш громіздким порівняно з чистим PHPUnit. Цей посібник охоплює патерни, важливі як для production-застосунків, так і для технічних співбесід.

Pest 5 як стандарт

Pest 5 (побудований на PHPUnit 13) є стандартним тестовим фреймворком для Laravel 13. Він вимагає PHP 8.4+ і пропонує рушій TIA, вбудовану верифікацію AI-агентів, інтеграцію з PHPStan та time-balanced sharding.

Налаштування Pest 5 у проєкті Laravel 13

Кожен новий застосунок Laravel 13 автоматично налаштовує Pest. Для існуючих проєктів міграція з Pest 4 займає хвилини, оскільки основним питанням є сумісність з PHPUnit 13. Конфігурація міститься у файлі tests/Pest.php, де реєструються глобальні трейти та допоміжні методи.

tests/Pest.phpphp
use IlluminateFoundationTestingRefreshDatabase;

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

Цей єдиний файл замінює старий трейт CreatesApplication та успадкування від базового тестового класу. Трейт RefreshDatabase обгортає кожен тест у транзакцію бази даних, автоматично відкочуючи зміни.

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() у Pest природно ланцюгується з assertions PHPUnit. Виклик assertJsonStructure валідує структуру відповіді, тоді як expect()->toBeTrue() підтверджує стан бази даних. Обидва стилі співіснують без конфліктів.

Test Impact Analysis: флагманська функція Pest 5

Рушій TIA робить Pest 5 трансформаційним для великих кодових баз. Під час першого запуску Pest записує, які тести торкаються яких файлів. Кожен наступний запуск виконує лише тести, на які вплинули зміни, і відтворює кешовані результати для решти.

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

# Subsequent runs: only affected tests execute
php artisan test

Test suite Laravel Cloud, що містить понад 19 000 тестів, скоротив час виконання з приблизно трьох хвилин до п'яти секунд з увімкненим TIA. Рушій розуміє більше ніж PHP-файли: він виявляє зміни в міграціях, шаблонах Blade та спільних JavaScript-компонентах.

TIA вимагає драйвера покриття (PCOV або Xdebug) для запису baseline. У CI pipeline'ах кешовані відображення зберігаються між запусками, що робить приріст продуктивності кумулятивним.

Unit-тести vs Feature-тести в Laravel

Різниця між unit та feature тестами в Laravel визначає, які частини фреймворку завантажуються. Unit-тести працюють без контейнера застосунку, що робить їх швидшими, але обмеженими чистою логікою. Feature-тести завантажують повний застосунок, забезпечуючи HTTP-виклики, запити до бази даних та розв'язання сервісів.

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);
    });
});

Unit-тести націлені на ізольовані класи без зовнішніх залежностей. Блок describe групує пов'язані assertions, і Pest 5 підтримує вкладені блоки describe для складних ієрархій тестів.

Feature-тести повинні складати більшість тестового набору Laravel. Вони виявляють інтеграційні помилки, які unit-тести пропускають, такі як неправильний middleware маршруту, відсутні правила валідації або пошкоджені зв'язки Eloquent. Загальне правило: якщо код торкається бази даних, HTTP-шару або фасаду, потрібно писати feature-тест.

Стратегії мокінгу з фасадами та Mockery

Мокінг у тестуванні Laravel ізолює тестований код від зовнішніх залежностей. Фасади Laravel надають вбудовані фейкові реалізації для черг, подій, сповіщень, пошти та сховища. Mockery обробляє все інше.

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 перехоплюють виклики на рівні фреймворку, запобігаючи фактичному надсиланню emails або dispatch jobs. Assertions на основі замикань перевіряють точні дані, передані кожному компоненту.

Мокайте на межах

Уникайте мокінгу внутрішніх доменних класів. Мокайте на межах: сторонні API, пошта, черги, файлові системи. Надмірний мокінг робить тести крихкими та тісно пов'язаними з деталями реалізації.

Для залежностей, які не є фасадами, інжектуйте їх через конструктор і використовуйте 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');
});

Прив'язка мока через $this->app->instance() замінює реальну реалізацію на час тесту. Mockery перевіряє, що charge було викликано рівно один раз з очікуваними аргументами.

Архітектурні тести з пресетами Pest

Pest 5 включає архітектурне тестування, яке забезпечує структурні правила по всій кодовій базі. Ці тести працюють з AST, а не з runtime, що робить їх надзвичайно швидкими.

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();

Перше правило забороняє контролерам напряму звертатися до бази даних, примушуючи використовувати патерн сервісного шару. Друге гарантує, що сервіси не можуть бути розширені, зменшуючи складність успадкування. Третє виявляє залишені debug-інструкції до потрапляння в production.

Також доступні пресети, специфічні для Laravel:

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

Ці три рядки забезпечують десятки правил: моделі розширюють правильний базовий клас, контролери не містять бізнес-логіки, немає небезпечних функцій на кшталт eval() чи md5() для хешування, та дотримуються стандартні конвенції PHP.

Готовий до співбесід з Laravel?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Mutation testing для перевірки якості тестів

Code coverage вимірює, які рядки виконуються під час тестів. Mutation testing йде далі: він модифікує вихідний код і перевіряє, чи тести виявляють зміну. Якщо мутація виживає, тестовий набір має прогалину.

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

Pest 5 вводить мутації, такі як зміна > на >=, видалення інструкцій return та інверсія булевих умов. Mutation score нижче 80% зазвичай вказує, що тести перевіряють результати без перевірки крайніх випадків.

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);
});

Перший тест проходить, навіть якщо логіка обчислення повністю неправильна, поки вона повертає float. Другий тест одразу падає, коли мутація змінює формулу. Специфічні assertions дають вищі mutation scores.

Патерни тестування бази даних та фабрики

Фабрики моделей Laravel генерують реалістичні тестові дані без ручного створення масивів. Pest 5 у поєднанні з фабриками забезпечує читабельну, легку в підтримці конфігурацію даних.

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();
});

Стан фабрики draft() встановлює значення за замовчуванням для неопублікованих статей. Ланцюгові assertions expect() з ->and() читаються як природна мова і падають з описовими повідомленнями.

Паралельне тестування та sharding

Запустіть php artisan test --parallel для виконання тестів у кількох процесах. Laravel автоматично створює окремі тестові бази даних для кожного процесу, запобігаючи конфліктам даних. Time-balanced sharding у Pest 5 розподіляє тести за фактичним часом виконання, а не за кількістю, забезпечуючи завершення кожного CI shard в один момент.

Типові питання співбесід про тестування Laravel

Технічні співбесіди на позиції Laravel часто перевіряють знання тестування. Ось питання, які з'являються найчастіше, разом з ключовими пунктами відповіді.

Яка різниця між fake(), mock() та spy() у тестуванні Laravel?

Фасадний fake() замінює весь фасад in-memory реалізацією (Mail::fake, Queue::fake). mock() через Mockery встановлює очікування перед виконанням і падає, якщо вони не виконуються. spy() записує взаємодії та дозволяє assertions після виконання, без встановлення попередніх очікувань. Вибір залежить від того, чи тест повинен перевіряти поведінку (mock), записувати взаємодії (spy) чи запобігати побічним ефектам (fake).

Чим відрізняється RefreshDatabase від DatabaseTransactions?

RefreshDatabase запускає міграції один раз і обгортає кожен тест у транзакцію. DatabaseTransactions припускає, що база даних вже має правильну схему, і лише обгортає тести в транзакції. RefreshDatabase безпечніший для CI pipeline'ів, де база даних може ще не існувати. DatabaseTransactions швидший, коли схема гарантовано актуальна.

Коли віддавати перевагу feature-тестам над unit-тестами?

Feature-тести повинні покривати будь-який код, який взаємодіє з HTTP-шаром, базою даних або сервісами Laravel. Unit-тести зарезервовані для чистих функцій та value objects без залежностей від фреймворку. У типовому Laravel-застосунку feature-тести переважають над unit-тестами у співвідношенні приблизно 3:1 або вище. Це відображає реальність, що більша частина коду Laravel за своєю природою інтегрована з фреймворком.

Як Test Impact Analysis покращує продуктивність CI?

TIA записує, які тести торкаються яких вихідних файлів під час першого запуску. При наступних запусках Pest виконує лише тести, на які вплинули змінені файли, і відтворює кешовані результати для незмінних тестів. Великі набори, які займали хвилини, можуть завершитися за секунди. Компроміс: TIA вимагає драйвера покриття (PCOV або Xdebug) для побудови карти залежностей, і перший запуск займає більше часу, ніж звичайний.

Як mutation testing покращує якість тестів понад code coverage?

Code coverage вимірює шляхи виконання. Тест може досягти 100% coverage, викликаючи кожен метод без будь-яких значущих assertions. Mutation testing модифікує вихідний код (змінюючи оператори, видаляючи return, інвертуючи булеві значення) і перевіряє, що принаймні один тест падає. Мутації, що вижили, виявляють надто слабкі або повністю відсутні assertions. Запуск php artisan test --mutate видає відсоток mutation score поряд зі стандартним coverage.

Більше питань співбесід Laravel можна знайти в банку питань SharpSkill, який охоплює автентифікацію, патерни service container, зв'язки Eloquent та архітектуру черг.

Тестування HTTP-відповідей та JSON assertions

HTTP testing helpers Laravel перевіряють status codes, headers, структури JSON та цілі redirect. У поєднанні з Pest вони формують лаконічні інтеграційні тести.

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']);
    });
});

Блок describe групує тести, пов'язані з автентифікацією. Кожна назва тесту описує очікувану поведінку, а не реалізацію. Метод assertJsonValidationErrors перевіряє, що конкретні поля мають повідомлення про помилки валідації.

Джерела

Ключові висновки про тестування Laravel з Pest 5

  • Pest 5 з Laravel 13 усуває boilerplate завдяки fluent API конфігурації, ланцюгам expect() та автоматичному виявленню тестів
  • Рушій TIA перезапускає лише affected тести, скорочуючи виконання великих наборів з хвилин до секунд без втрати точності coverage
  • Feature-тести повинні складати ядро тестового набору Laravel, з unit-тестами, зарезервованими для ізольованої бізнес-логіки
  • Facade fakes (Mail::fake(), Queue::fake()) обробляють мокінг на рівні фреймворку, тоді як Mockery обробляє сторонні залежності, інжектовані через контейнер
  • Архітектурні тести забезпечують структурні правила (без Eloquent у контролерах, без debug-функцій) без runtime overhead
  • Mutation testing з --mutate виявляє слабкі assertions, які сам code coverage пропускає
  • Factory states та ланцюгові assertions expect()->and() зберігають конфігурацію тестових даних читабельною, а assertions специфічними
  • Для підготовки до співбесід Laravel розуміння mock/fake/spy, поведінки RefreshDatabase, TIA та mutation testing демонструє зрілість тестування понад базовий coverage

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Щоденний виклик

Чи знайдеш ти помилку в Laravel?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 2 вересня 2026 р.

Теги

#laravel
#testing
#pest
#php
#interview

Поділитися

Пов'язані статті