Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Коли я тільки почав працювати з Laravel, тестування здавалося мені зайвою витратою часу. “Навіщо писати тести, якщо можна просто перевірити в браузері?” — думав я наївно. Але після першого серйозного рефакторингу, коли зламалася половина функціоналу, я зрозумів цінність автоматизованого тестування. У цій статті поділюся практиками тестування в Laravel, які реально працюють і допомагають спати спокійно після деплою.
Laravel надає потужний інструментарій для тестування прямо “з коробки”. Фреймворк побудований на PHPUnit та додає власні зручні методи для тестування HTTP-запитів, роботи з базою даних, чергами та іншими компонентами. Це не просто nice-to-have — це фундамент надійного додатка.
Є три основні рівні тестування, які я використовую у проектах: юніт-тести (перевіряють окремі класи та методи), feature-тести (тестують цілі функціонали через HTTP), та інтеграційні тести (перевіряють взаємодію компонентів). Laravel робить всі три типи тестування досить простими.
Перше, з чого варто почати — правильна структура тестів. Laravel створює дві папки: tests/Unit для юніт-тестів та tests/Feature для feature-тестів. Але в реальних проектах я створюю додаткові підпапки:
tests/
├── Feature/
│ ├── Auth/
│ │ ├── LoginTest.php
│ │ └── RegisterTest.php
│ ├── Api/
│ │ └── UserApiTest.php
│ └── Admin/
│ └── DashboardTest.php
├── Unit/
│ ├── Models/
│ │ └── UserTest.php
│ ├── Services/
│ │ └── PaymentServiceTest.php
│ └── Helpers/
│ └── DateHelperTest.php
└── TestCase.phpТака структура дозволяє швидко знайти потрібний тест і зрозуміти, що саме він перевіряє. Назви файлів завжди закінчуються на Test.php — це конвенція PHPUnit.
Юніт-тести — це фундамент пірамід тестування. Вони швидкі, ізольовані і перевіряють конкретну логіку без залежностей. Ось приклад типового юніт-тесту для моделі:
namespace Tests\Unit\Models;
use App\Models\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
/** @test */
public function it_can_get_full_name()
{
$user = new User([
'first_name' => 'Олег',
'last_name' => 'Петренко'
]);
$this->assertEquals('Олег Петренко', $user->full_name);
}
/** @test */
public function it_checks_if_user_is_admin()
{
$user = new User(['role' => 'admin']);
$this->assertTrue($user->isAdmin());
$regularUser = new User(['role' => 'user']);
$this->assertFalse($regularUser->isAdmin());
}
}Code language: PHP (php)Зверніть увагу на анотацію @test або префікс test_ в назві методу — PHPUnit розпізнає ці методи як тести. Я віддаю перевагу анотаціям, бо назви методів виходять більш читабельними.
Для складної бізнес-логіки створюю окремі сервісні класи і тестую їх:
namespace Tests\Unit\Services;
use Tests\TestCase;
use App\Services\DiscountCalculator;
class DiscountCalculatorTest extends TestCase
{
private DiscountCalculator $calculator;
protected function setUp(): void
{
parent::setUp();
$this->calculator = new DiscountCalculator();
}
/** @test */
public function it_calculates_percentage_discount()
{
$price = 1000;
$discount = 15; // 15%
$result = $this->calculator->apply($price, $discount);
$this->assertEquals(850, $result);
}
}Code language: PHP (php)Метод setUp() виконується перед кожним тестом — ідеальне місце для ініціалізації об’єктів, які використовуються в кількох тестах.


Feature-тести — це те, де Laravel по-справжньому сяє. Ви можете емулювати HTTP-запити, перевіряти редіректи, статус-коди, вміст відповідей — все без запуску браузера. Ось тест для процесу реєстрації:
namespace Tests\Feature\Auth;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegisterTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function user_can_register_with_valid_data()
{
$response = $this->post('/register', [
'name' => 'Іван Коваль',
'email' => 'ivan@example.com',
'password' => 'SecurePass123',
'password_confirmation' => 'SecurePass123'
]);
$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', [
'email' => 'ivan@example.com'
]);
}
/** @test */
public function registration_fails_with_invalid_email()
{
$response = $this->post('/register', [
'name' => 'Тест',
'email' => 'invalid-email',
'password' => 'password123',
'password_confirmation' => 'password123'
]);
$response->assertSessionHasErrors('email');
$this->assertDatabaseCount('users', 0);
}
}Code language: PHP (php)Трейт RefreshDatabase — це магія Laravel. Він автоматично створює тестову базу даних перед тестами і відкочує всі зміни після кожного тесту. Ваша реальна база залишається чистою.
Для API-endpoints Laravel надає спеціальні методи перевірки JSON-відповідей:
/** @test */
public function it_returns_user_list_as_json()
{
User::factory()->count(3)->create();
$response = $this->getJson('/api/users');
$response->assertStatus(200)
->assertJsonCount(3, 'data')
->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'email']
]
]);
}Code language: PHP (php)Метод assertJsonStructure() перевіряє структуру відповіді, не прив’язуючись до конкретних значень — дуже зручно для динамічних даних.
Одна з найпотужніших можливостей тестування в Laravel — це фабрики моделей. Замість ручного створення тестових даних, використовуйте factories:
// database/factories/UserFactory.php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
public function definition()
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => bcrypt('password'),
'remember_token' => Str::random(10),
];
}
// Можна додавати стани
public function admin()
{
return $this->state(fn (array $attributes) => [
'role' => 'admin',
]);
}
}Code language: PHP (php)Тепер у тестах створення даних стає тривіальним:
// Створити одного користувача
$user = User::factory()->create();
// Створити 10 користувачів
$users = User::factory()->count(10)->create();
// Створити адміна
$admin = User::factory()->admin()->create();
// Створити без збереження в БД (для юніт-тестів)
$user = User::factory()->make();Code language: PHP (php)Переваги використання фабрик:
Недоліки та підводні камені:
Коли потрібно протестувати код, що взаємодіє із зовнішніми сервісами (платіжні системи, API, email), використовуйте мокування. Laravel інтегрує Mockery для цього:
use App\Services\PaymentGateway;
use Mockery\MockInterface;
/** @test */
public function it_processes_payment_successfully()
{
// Мокуємо PaymentGateway
$this->mock(PaymentGateway::class, function (MockInterface $mock) {
$mock->shouldReceive('charge')
->once()
->with(1000, 'user@example.com')
->andReturn(['status' => 'success', 'transaction_id' => '12345']);
});
$response = $this->post('/checkout', [
'amount' => 1000,
'email' => 'user@example.com'
]);
$response->assertStatus(200);
$this->assertDatabaseHas('transactions', [
'status' => 'success'
]);
}Code language: PHP (php)Це дозволяє тестувати логіку без реальних викликів платіжних API. Для email Laravel надає фасад Mail з методом fake():
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderShipped;
/** @test */
public function it_sends_order_confirmation_email()
{
Mail::fake();
// Виконуємо дію, що має відправити email
$order = Order::factory()->create();
$order->ship();
// Перевіряємо, що email був "відправлений"
Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
return $mail->order->id === $order->id;
});
}Code language: PHP (php)TDD (Test-Driven Development) — це підхід, коли ви пишете тести перед кодом. Звучить дивно, але працює. Цикл простий: спочатку пишете тест (він падає), потім пишете мінімальний код для проходження тесту, потім рефакторите.
Приклад: потрібно створити метод для розрахунку загальної вартості замовлення з податком. Спочатку тест:
/** @test */
public function it_calculates_total_with_tax()
{
$order = new Order();
$order->subtotal = 100;
$order->tax_rate = 0.2; // 20%
$this->assertEquals(120, $order->calculateTotal());
}Code language: PHP (php)Тест не пройде, бо методу calculateTotal() ще не існує. Тепер створюємо його:
class Order extends Model
{
public function calculateTotal()
{
return $this->subtotal + ($this->subtotal * $this->tax_rate);
}
}Code language: PHP (php)Тест проходить! Можна рефакторити, якщо потрібно, але тест залишається зеленим.


Code coverage показує відсоток коду, який виконується під час тестів. Це корисна метрика, але не абсолютна. 100% покриття не гарантує відсутність багів, але низьке покриття — чіткий сигнал проблеми.
Для генерації звіту code coverage використовуйте Xdebug або PCOV:
php artisan test --coverage
# Або для HTML-звіту
php artisan test --coverage-html=coverage-reportCode language: PHP (php)У проектах я встановлюю мінімум 70% покриття для критичних модулів (аутентифікація, платежі, бізнес-логіка) і 50% для решти. Це реалістичні цілі, які дають баланс між безпекою та продуктивністю команди.
Тести мають запускатися автоматично при кожному push або pull request. Я використовую GitHub Actions для цього:
# .github/workflows/tests.yml
name: Laravel Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, pdo, pdo_mysql
- name: Install dependencies
run: composer install
- name: Run tests
run: php artisan testCode language: PHP (php)Тепер кожен комміт автоматично перевіряється. Якщо тести не проходять — merge блокується. Це запобігає потраплянню бажаного коду в main гілку.
Ось практики, які я завжди дотримуюсь у проектах:
it_prevents_unauthorized_access_to_admin_panel.Плюси комплексного підходу до тестування:
Мінуси та виклики:
Окрім вбудованих можливостей, є чудові інструменти, які роблять тестування ще зручнішим:
Детальніше про корисні інструменти можна дізнатися в статті про топ-5 інструментів для Laravel-розробника.
Тестування в Laravel — це не витрата часу, а інвестиція. Так, спочатку доведеться витратити більше часу на написання тестів. Але згодом ви заощадите набагато більше на відлагодженні, виправленні багів у production та безсонних ночах після невдалого деплою.
Почніть з малого: напишіть тести для найкритичніших частин вашого додатка. Аутентифікація, платежі, обробка даних користувачів — це мінімум, який має бути покритий тестами. Потім поступово розширюйте покриття.
Пам’ятайте: тести — це не про досягнення 100% покриття. Це про впевненість, що ваш код робить те, що має робити, і продовжує це робити після кожної зміни. А Laravel робить процес тестування настільки зручним, що немає причин цим не користуватися.
Якщо ви тільки розпочинаєте з Laravel, рекомендую спочатку ознайомитись з налаштуванням Laravel на macOS та вибрати зручну IDE для PHP, щоб створити комфортне середовище для розробки та тестування.