Привет, Хабр!

Laravel — это целая экосистема, которая позволяет разрабатывать качественные веб-приложения. Но вот вопрос: как быть уверенным, что все эти красивые строки кода не развалятся в самый неподходящий момент? Ответ очевиден — тесты!

Какие тесты бывают в Laravel

Laravel поддерживает три главных типа тестов: юнит-тесты, интеграционные тесты и функциональные тесты. Каждому своё, каждый важен.

Юнит-тесты

Юнит-тесты — маленькие тесты для маленьких частей кода: методов, функций, классов. Они изолированы от всего остального — ни базы данных, ни API не трогаем, только чистая логика.

Пример? Пожалуйста:

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use App\Services\DiscountService;

class DiscountServiceTest extends TestCase
{
    public function test_calculate_discount()
    {
        $price = 100;
        $expectedDiscount = 90; // 10% скидки

        $service = new DiscountService();
        $actualDiscount = $service->calculateDiscount($price);

        $this->asnamespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use App\Services\DiscountService;

class DiscountServiceTest extends TestCase
{
    public function test_calculate_discount()
    {
        $price = 100;
        $expectedDiscount = 90; // 10% скидки

        $service = new DiscountService();
        $actualDiscount = $service->calculateDiscount($price);

        $this->assertEquals($expectedDiscount, $actualDiscount);
    }
}
sertEquals($expectedDiscount, $actualDiscount);
    }
}

Здесь тестируем метод calculateDiscount. 100 рублей на входе — 90 на выходе. Всё по плану.

Интеграционные тесты

Интеграционные тесты — это когда ты смотришь, как всё взаимодействует в связке. Вот у тебя есть сервис, который лезет в БД, обращается к внешнему API и возвращает что-то вкусное. Тут юнит-тесты уже не помогут — нужно посмотреть на всю картину в целом.

Например, есть сервис, который получает данные от API и сохраняет их в базу. Проверим, как это работает:

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Services\ExternalApiService;

class ApiIntegrationTest extends TestCase
{
    use RefreshDatabase;

    public function test_external_api_data_is_saved_to_database()
    {
        $this->mock(ExternalApiService::class, function ($mock) {
            $mock->shouldReceive('fetchData')
                 ->once()
                 ->andReturn([
                     'name' => 'Test Item',
                     'price' => 100
                 ]);
        });

        $response = $this->get('/api/fetch-data');

        $response->assertStatus(200);
        $this->assertDatabaseHas('items', [
            'name' => 'Test Item',
            'price' => 100
        ]);
    }
}

Здесь мы имитируем то, что наш API вернул данные, и проверяем, что они сохранились в базе.

Функциональные тесты

Функциональные тесты — это взгляд на приложения глазами пользователя. Тут пригодится Laravel Dusk — инструмент для браузерных тестов.

Допустим, есть форма входа. Проверим, что она работает корректно:

namespace Tests\Browser;

use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class LoginTest extends DuskTestCase
{
    public function test_user_can_login()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                    ->type('email', 'test@example.com')
                    ->type('password', 'password')
                    ->press('Login')
                    ->assertPathIs('/home');
        });
    }
}

Этот тест имитирует реального пользователя: он заходит на страницу логина, вводит email и пароль, а затем проверяет, что после успешного входа его перенаправляют на страницу /home.

Тестирование API

Если есть API его нужно его тестировать, то ларавелл умеет проверять JSON-запросы и ответы. Вот, например, как мы проверим список продуктов:

namespace Tests\Feature;

use Tests\TestCase;

class ProductsApiTest extends TestCase
{
    public function test_get_products()
    {
        $response = $this->getJson('/api/products');

        $response->assertStatus(200)
                 ->assertJsonStructure([
                     '*' => ['id', 'name', 'price']
                 ]);
    }
}

Этот тест проверяет, что API возвращает список продуктов с нужными полями.

Примеры использования

Тестирование отправки письма при успешной регистрации

Следующий пример — это классическая ситуация, когда при регистрации нового пользователя система должна отправить приветственное письмо. Нужно убедиться, что оно действительно отправляется.

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

class WelcomeEmailTest extends TestCase
{
    use RefreshDatabase;

    public function test_welcome_email_is_sent_upon_registration()
    {
        Mail::fake();

        // Выполняем регистрацию пользователя
        $response = $this->post('/register', [
            'name' => 'Ivan Ivanov',
            'email' => 'ivan@example.com',
            'password' => 'password123',
            'password_confirmation' => 'password123'
        ]);

        // Проверяем, что пользователь успешно создан
        $this->assertDatabaseHas('users', ['email' => 'ivan@example.com']);

        // Проверяем, что письмо было отправлено
        Mail::assertSent(WelcomeEmail::class, function ($mail) {
            return $mail->hasTo('ivan@example.com');
        });
    }
}

Тест позволит убедиться, что система действительно отправляет email каждому новому пользователю.

Тестирование удаления записей из базы данных

Предположим, есть CRUD-система для управления записями. Нужно убедиться, что записи корректно удаляются, а также правильно обрабатываются сопутствующие данные. Протестируем сценарий удаления записи и проверим, что данные действительно ушли в никуда.

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Post;

class PostDeletionTest extends TestCase
{
    use RefreshDatabase;

    public function test_post_can_be_deleted()
    {
        // Создаем пост
        $post = Post::factory()->create();

        // Отправляем запрос на удаление
        $response = $this->delete('/posts/' . $post->id);

        // Проверяем успешный статус и редирект
        $response->assertStatus(302);
        $response->assertRedirect('/posts');

        // Проверяем, что запись удалена из базы
        $this->assertDatabaseMissing('posts', ['id' => $post->id]);
    }
}

Этот тест проверяет:

  1. Успешное удаление записи.

  2. Отсутствие записи в базе данных после удаления.

Тест защитит от сценариев, когда запись вроде бы удаляется, но остаётся в базе.


Всех желающих приглашаем на открытый урок «Использование GraphQL в Laravel проектах». На нем сравним RESTFul и Graphql; предоставим API, используя Graphql; а также посмотрим, как работать с Graphql на фронтэнде. Записаться на урок можно на странице курса "Framework Laravel".

Комментарии (0)