Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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

Пріоритет запитів (від кращого до гіршого):

  1. getByRole з name - роль доступності (кнопка, посилання, поле, заголовок) і доступна назва. Так елементи знаходять і користувачі зчитувачів екрана;
  2. getByLabelText - поля форми за підписом;
  3. getByPlaceholderText, getByText, getByDisplayValue;
  4. getByAltText, getByTitle;
  5. 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, тож оновлення стану після події застосовуються до наступної перевірки.

Докладніше в документації: user-event: вступ

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: налаштування