Middle: питання на співбесіді з теми «Тестування»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Laravel має тестування з коробки поверх PHPUnit; популярна надбудова - Pest із лаконічним синтаксисом.
it('creates a post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'title' => 'Hello',
]);
$response->assertRedirect();
$this->assertDatabaseHas('posts', ['title' => 'Hello']);
});
- Feature-тести перевіряють HTTP-флоу (більшість тестів); Unit - окремі класи ізольовано.
- Трейт
RefreshDatabaseізолює тести, відкочуючи зміни після кожного; дані генерують фабрики. actingAs($user)автентифікує користувача для захищених маршрутів.
Корисні асерції: assertStatus, assertSee, assertJson, assertDatabaseHas, assertRedirect, assertAuthenticated.
Мокінг - ізоляція від зовнішніх ефектів (API, листи, черги):
Mail::fake(); Mail::assertSent(OrderShipped::class);
Queue::fake(); Queue::assertPushed(ProcessPodcast::class);
Notification::fake(); Http::fake();
// мок сервісу через контейнер
$this->mock(PaymentGateway::class)
->shouldReceive('charge')->once()->andReturn(true);
Запуск: php artisan test, --filter=PostTest, --parallel (швидше).
Підмінити відповідний фасад фейком: він перехопить відправку, а тест перевірить, що вона сталася з правильними даними.
it('notifies the customer when the order ships', function () {
Notification::fake();
Mail::fake();
Queue::fake();
$order = Order::factory()->create();
$order->ship();
Notification::assertSentTo($order->customer, OrderShipped::class);
Mail::assertSent(InvoiceMail::class, fn ($mail) => $mail->hasTo($order->customer->email));
Queue::assertPushed(SyncWithWarehouse::class);
});
Які фейки є: Mail, Notification, Queue, Bus (бачить ще й ланцюжки й пакети), Event, Storage, Http, Process, Exceptions.
Пастки:
Event::fake()без аргументів глушить усі події, включно з подіями моделей, на яких можуть триматися генерація UUID чиObserver. Підробляйте лише потрібні:Event::fake([OrderShipped::class]).- Фейк не перевіряє, що робота виконується.
Queue::assertPushed()доводить, що завдання поставлено, але не що йогоhandle()працює. Код самого завдання тестують окремо - викликаючи його напряму. - Mailable можна перевірити без відправки:
(new InvoiceMail($order))->assertSeeInHtml($order->number).
HTTP-тести Laravel мають методи для JSON-запитів і перевірки відповіді.
it('creates a post via the API', function () {
Sanctum::actingAs(User::factory()->create(), ['posts:write']);
$this->postJson('/api/posts', ['title' => 'Черги в Laravel'])
->assertCreated()
->assertJsonPath('data.title', 'Черги в Laravel')
->assertJsonStructure(['data' => ['id', 'title', 'created_at']]);
});
it('rejects a post without a title', function () {
Sanctum::actingAs(User::factory()->create(), ['posts:write']);
$this->postJson('/api/posts', [])
->assertUnprocessable()
->assertInvalid(['title']);
});
Що варто знати:
postJson()ставитьAccept: application/json, тож помилки валідації приходять як 422 з JSON, а не редиректом.assertJsonPath()перевіряє одне значення за шляхом,assertJson()- частковий збіг,assertExactJson()- точний.- Флюентні перевірки дозволяють описати структуру з типами й гарантувати, що зайвих полів немає:
->assertJson(fn (AssertableJson $json) => $json
->where('data.id', $post->id)
->whereType('data.title', 'string')
->missing('data.author.password')
->etc());
- Автентифікація:
actingAs()для сесії,Sanctum::actingAs()з переліком здатностей для токенів.
Керувати часом застосунку з тесту, а не чекати.
it('expires a subscription after 30 days', function () {
$this->freezeTime();
$subscription = Subscription::factory()->create();
$this->travel(31)->days();
expect($subscription->fresh()->isExpired())->toBeTrue();
});
Інструменти:
freezeTime()- зупиняєnow(), щоб час не зсувався між рядками тесту.travel(5)->minutes(),travelTo($date),travelBack()- зсувають час застосунку.- Те саме вміє
Carbon::setTestNow().
Що варто винести з цього: такий тест працює, лише поки код бере час через now() чи Carbon. Виклики time(), date() чи new \DateTime() бачать справжній годинник і тест обійдуть.
Чистіший шлях для доменного коду - передавати момент явно:
public function isExpired(CarbonImmutable $now): bool
{
return $this->ends_at->lessThan($now);
}
Тоді тест взагалі не потребує підміни часу, а метод стає чистою функцією. Корисні випадки для перевірки: межі (рівно 30 днів), перехід доби, часові пояси й літній час.
Підмінити HTTP-клієнт Laravel фейковими відповідями, щоб тест не залежав від мережі й чужого сервісу.
it('imports exchange rates', function () {
Http::fake([
'api.rates.example/*' => Http::response(['usd' => 41.2], 200),
'*' => Http::response(status: 500),
]);
(new ImportRates)->handle();
expect(Rate::latest()->value('usd'))->toBe(41.2);
Http::assertSent(fn (Request $request) => str_contains($request->url(), 'api.rates.example')
&& $request->hasHeader('Authorization'));
});
Що варто знати:
- Шаблони URL з
*підмінюють групи запитів. Запит, що не збігся з жодним шаблоном, піде в мережу насправді. Http::preventStrayRequests()забороняє такі запити: будь-який непідмінений кидатиме виняток. Його вмикають уTestCaseчиPest.phpдля всього набору.Http::sequence()повертає різні відповіді на послідовні виклики - так перевіряють повтори після помилки.- Помилки теж тестують: таймаут, 429, 500 - саме там зазвичай ховаються баги обробки.
Межа фейку: він закріплює ваше уявлення про чужий API. Коли провайдер змінить формат, тести лишаться зеленими - тому корисні контрактні перевірки чи хоча б моніторинг справжніх відповідей у проді.