Junior: питання на співбесіді з теми «Тестування»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
React Testing Library побудована на принципі: чим більше тести схожі на те, як застосунок використовують, тим більше впевненості вони дають. Тест взаємодіє з компонентом як користувач - бачить текст і кнопки, натискає, вводить, - а не перевіряє стан, props чи внутрішні методи.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('додає товар у кошик', async () => {
const user = userEvent.setup();
render(<Product product={{ id: 1, title: 'Чашка', price: 250 }} />);
await user.click(screen.getByRole('button', { name: 'Додати в кошик' }));
expect(screen.getByRole('status')).toHaveTextContent('У кошику: 1');
});
Пріоритет запитів (від кращого до гіршого):
getByRoleзname- роль доступності (кнопка, посилання, поле, заголовок) і доступна назва. Так елементи знаходять і користувачі зчитувачів екрана;getByLabelText- поля форми за підписом;getByPlaceholderText,getByText,getByDisplayValue;getByAltText,getByTitle;getByTestId- лише коли інших способів немає.
Чому роль, а не клас чи data-testid:
- тест не ламається від рефакторингу: змінили розмітку, класи Tailwind, структуру - тест проходить, поки кнопка лишається кнопкою з тим самим текстом;
- тест перевіряє доступність: якщо кнопку зробили
<div onClick>,getByRole('button')її не знайде - і це правильно, бо клавіатурою та зчитувачем екрана її теж не використати; getByLabelTextпадає, якщо поле не пов'язане з підписом, - ще одна безкоштовна перевірка.
Варіанти запитів:
getBy...- елемент має бути, інакше помилка;queryBy...- повертаєnull, для перевірки відсутності (expect(screen.queryByText('Помилка')).not.toBeInTheDocument());findBy...- чекає на появу (асинхронно);getAllBy...тощо - кілька елементів.
screen замість деструктуризації результату render - коротше й не треба оновлювати деструктуризацію.
Налагодження: screen.debug() друкує поточний DOM, а screen.logTestingPlaygroundURL() дає посилання, де можна підібрати найкращий запит для елемента.
Докладніше в документації: Testing Library: пріоритет запитів
Обидва викликають події в тесті, але на різному рівні.
fireEvent відправляє одну конкретну подію DOM:
fireEvent.change(input, { target: { value: 'Оля' } });
fireEvent.click(button);
change одразу встановлює значення - без фокусу, без натискань клавіш, без подій keydown/input/keyup для кожної літери.
userEvent імітує дії користувача - послідовність подій, яку згенерував би браузер:
import userEvent from '@testing-library/user-event';
test('пошук', async () => {
const user = userEvent.setup();
render(<Search />);
await user.type(screen.getByRole('searchbox'), 'laravel');
await user.keyboard('{Enter}');
await user.click(screen.getByRole('button', { name: 'Очистити' }));
});
user.type для кожної літери викликає keydown, keypress, input, keyup, переводить фокус на поле кліком; user.click - pointerdown, mousedown, focus, pointerup, mouseup, click.
Чому userEvent кращий за замовчуванням:
- ловить реальні помилки: не дасть клікнути кнопку з
disabledчиpointer-events: none, не введе текст у поле лише для читання.fireEvent.clickспрацює на заблокованій кнопці - тест пройде, а користувач натиснути не зможе; - обробники, що реагують на
keydownчиfocus, отримують свої події; - вбудовані дії:
user.selectOptions,user.upload,user.tab()(перевірка порядку фокусу),user.hover,user.clear,user.paste.
Правила роботи з userEvent 14:
- створювати екземпляр через
userEvent.setup()на початку тесту (доrender); - усі методи асинхронні - обов'язково
await; - з фальшивими таймерами -
userEvent.setup({ advanceTimers: vi.advanceTimersByTime }), інакше тест «зависне» на внутрішніх затримках.
Коли fireEvent доречний:
- подія, яку
userEventне підтримує (scroll,resize, власні події, події медіа); - точне відтворення однієї події для перевірки обробника.
Обидва вже обгорнуті в act, тож оновлення стану після події застосовуються до наступної перевірки.
Vitest - тест-раннер на основі Vite: використовує ту саму конфігурацію (псевдоніми, плагіни, TypeScript, JSX), що й застосунок, тож окремо налаштовувати Babel чи трансформації не треба. API сумісний з Jest (describe, test, expect, vi.fn).
Встановлення:
npm i -D vitest jsdom @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
@testing-library/dom - обов'язкова залежність, яку з версії 16 React Testing Library треба ставити явно.
Конфігурація:
// vite.config.ts (або vitest.config.ts)
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom', // DOM для компонентів у Node.js
globals: true, // describe/test/expect без імпорту
setupFiles: ['./src/test/setup.ts'],
},
});
// src/test/setup.ts
import '@testing-library/jest-dom/vitest'; // toBeInTheDocument, toHaveTextContent...
jsdom - реалізація DOM на JavaScript: є document, події, форми. Немає реальної розкладки (getBoundingClientRect повертає нулі), IntersectionObserver, matchMedia - їх доводиться підміняти. Альтернатива - happy-dom (швидша, менш повна).
Очищення між тестами: React Testing Library автоматично демонтує компоненти після кожного тесту, якщо раннер має глобальний afterEach (з globals: true). Без глобальних функцій потрібен ручний afterEach(cleanup).
Мінімальний тест:
import { render, screen } from '@testing-library/react';
test('показує привітання', () => {
render(<Greeting name="Оля" />);
expect(screen.getByRole('heading')).toHaveTextContent('Привіт, Оля');
});
Корисні налаштування:
TypeScript: додати"types": ["vitest/globals", "@testing-library/jest-dom"]уtsconfig, щоб редактор знав глобальні функції й матчери;test.css: false(за замовчуванням CSS не обробляється) - тести не залежать від стилів;vitest --ui- зручний інтерфейс у браузері,vitest --coverage- покриття.
Jest працює з тими самими бібліотеками, але потребує окремого налаштування трансформацій (Babel чи ts-jest) і модулів - у проєктах на Vite Vitest простіший.
Докладніше в документації: React Testing Library: налаштування