Модуль 34

Тестування React-компонентів

Ось готовий урок, створений спеціально для тебе в стилі Девіда Малана (CS50). Вмикаймо уяву, ми в аудиторії Сандерс, і поїхали! 🚀


🧪 Тестування React-компонентів: Ваша подушка безпеки

Вітаю, друзі! Це CS50... тобто, це ваш урок з React!

1. 🔥 Вступ: Чому ми взагалі про це говоримо?

Уявіть ситуацію. П'ятниця, вечір. Ви тільки-но закінчили роботу над крутою фічею для вашого інтернет-магазину — кнопкою "Купити в один клік". Ви перевірили її — працює! Ви "пушите" код на сервер, закриваєте ноутбук і йдете пити пиво з друзями.

А в суботу вранці вас будить дзвінок розлюченого менеджера: "Сайт лежить! Ніхто не може нічого купити! Що ти наробив?!"

Виявляється, ваша "маленька зміна" випадково зламала логін користувача. Але ви ж це не перевіряли, правда? Ви перевіряли лише кнопку.

Питання до вас: Чи хотіли б ви мати робота, який за 3 секунди переклацає весь ваш сайт, кожну кнопочку, кожне поле вводу, і скаже: "Все ок, друже, нічого не зламалося"?

Звісно, хотіли б.

Без тестування розробка схожа на будівництво хмарочоса без будівельного рівня. Може, воно і стоїть рівно... поки що. Але чи ризикнете ви туди зайти?

Сьогодні ми навчимося будувати цю "подушку безпеки" для ваших React-компонентів.


2. 🧠 Теоретична база: Що там "під капотом"?

Ми не будемо заглиблюватися в нудну термінологію, але є два інструменти, імена яких ви маєте знати. Це як Бетмен і Робін у світі тестів:

  1. Jest — це наш "запускач". Він знаходить файли з тестами, виконує їх і каже: ✅ Passed або ❌ Failed.
  2. React Testing Library (RTL) — це інструмент, який допомагає нам "рендерити" компоненти та взаємодіяти з ними так, як це робить жива людина.

Як це працює? (Інтуїтивне пояснення)

Коли ви пишете код React, він живе у браузері (Chrome, Firefox). Там є DOM-дерево. Але тести запускаються в консолі (терміналі). Там немає браузера!

Як же ми тестуємо? Ми використовуємо JSDOM — це симуляція браузера всередині Node.js. RTL бере ваш компонент <Button />, створює його віртуальну HTML-версію в пам'яті комп'ютера і дозволяє нам питати: "Гей, а там є текст 'Натисни мене'?" або "А що буде, якщо я клікну сюди?".

Золоте правило RTL: Тестуйте так, як користується програмою юзер, а не так, як вона написана всередині. Юзер не знає про ваші state, props чи useEffect. Він бачить кнопки і текст. Отже, і тести мають шукати кнопки і текст.


3. 🧪 Приклади: Від "Hello World" до кліків

Приклад 1: Простий рендер

У нас є компонент Greeting, який просто вітає користувача.

// Greeting.js
const Greeting = ({ name }) => {
  return <h1>Привіт, {name}!</h1>;
};
export default Greeting;

Як ми це протестуємо? Нам треба: 1. "Намалювати" компонент. 2. Знайти текст на "екрані". 3. Переконатися, що текст правильний.

// Greeting.test.js
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';

test('має показувати правильне привітання', () => {
  // 1. Рендеримо
  render(<Greeting name="Андрій" />);

  // 2. Шукаємо елемент (якщо не знайде — тест впаде)
  const element = screen.getByText(/Привіт, Андрій!/i);

  // 3. Стверджуємо (Assertion)
  expect(element).toBeInTheDocument();
});

Бачите, як просто? Це майже англійська мова: "Expect element to be in the document".


Приклад 2: Інтерактивність (Кнопка)

Ускладнимо задачу. Компонент з лічильником.

// Counter.js
import { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Рахунок: {count}</p>
      <button onClick={() => setCount(count + 1)}>Збільшити</button>
    </div>
  );
};
export default Counter;

Питання до студента: Що ми маємо зробити в тесті, щоб перевірити, чи працює кнопка? (Пауза для роздумів...) Правильно! Нам треба: знайти кнопку, "клікнути" на неї програмно і перевірити, чи змінився текст.

// Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('збільшує лічильник при кліку', () => {
  render(<Counter />);

  // Перевіряємо початковий стан
  const counterText = screen.getByText(/Рахунок: 0/i);
  expect(counterText).toBeInTheDocument();

  // Знаходимо кнопку
  const button = screen.getByText(/Збільшити/i);

  // Імітуємо клік! (Fire Event - "Вистрілити подією")
  fireEvent.click(button);

  // Перевіряємо результат
  expect(screen.getByText(/Рахунок: 1/i)).toBeInTheDocument();
});

Чому результат такий? Тому що fireEvent.click(button) змушує React запустити функцію onClick, змінити стейт і перерендерити віртуальний DOM.


4. 🛠 Практична частина

Час забруднити руки кодом! Ось ваші завдання.

Завдання 1: "Копіпаст-майстер" Створіть компонент StatusMessage, який приймає проп isSuccess. Якщо true — показує "Успіх!", якщо false — "Помилка!". Напишіть два тести для обох сценаріїв.

Завдання 2: "Зникнення" У вас є кнопка, яка при натисканні зникає (не просто ховається CSS-ом, а взагалі видаляється з DOM). Підказка: Використовуйте queryByText замість getByText, щоб перевірити відсутність елемента (воно поверне null, а не помилку). expect(screen.queryByText('Кнопка')).not.toBeInTheDocument();

Завдання 3: "Шерлок Холмс" У вас є інпут: <input placeholder="Введіть email" />. Напишіть тест, який знаходить цей інпут саме за плейсхолдером (getByPlaceholderText), вводить туди текст і перевіряє, чи змінилося значення (value).

Завдання 4: Міні-кейс Створіть компонент Spoiler. Він показує кнопку "Показати секрет". Коли клікаєш — з'являється текст "React — це просто JS". Напишіть тест: 1. Тексту спочатку немає. 2. Клікаємо кнопку. 3. Текст з'явився.

Завдання 5: А що, якщо? Що буде, якщо в тесті з Прикладу 2 ми зробимо fireEvent.click(button) два рази підряд? Який текст ми маємо очікувати? Перевірте це на практиці.


5. 💡 Мислення як у розробника

Послухайте уважно. Новачки часто роблять одну й ту ж помилку. Вони намагаються тестувати реалізацію, а не поведінку.

❌ Поганий тест (як думає новачок): "Я хочу перевірити, чи змінився state компонента на true." (Це погано, бо якщо ви перейменуєте змінну стейту, тест впаде, хоча програма працює).

✅ Хороший тест (як думає сеньйор): "Я хочу перевірити, чи побачив користувач повідомлення про помилку після кліку." (Нам байдуже, як це зроблено всередині — через useState, Redux чи магію вуду. Головне — результат для юзера).

Порада з практики: Якщо ваш тест занадто складний і займає 50 рядків коду для простої кнопки — швидше за все, ви робите щось не так. Тест має бути простою історією: Підготував -> Зробив дію -> Перевірив результат.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили?

  1. Зрозуміли, що тести — це не бюрократія, а наш спокійний сон.
  2. Навчилися рендерити компоненти у "віртуальному браузері".
  3. Навчилися знаходити елементи та клікати по них програмно.
  4. Зрозуміли філософію: Тестуй як юзер, а не як програміст.

Тепер ви вмієте писати базові юніт-тести. Ви вже кращі за 50% новачків, які "тестують" свій код, просто оновлюючи сторінку в браузері 100 разів на день.

Тизер наступного уроку: Але що робити, коли наш компонент хоче завантажити дані з сервера (API)? Ми ж не хочемо робити реальні запити в тестах? На наступному уроці ми поговоримо про Mocking (Моки) — мистецтво створення "фейкових" серверів.

А поки що — пишіть код, ламайте його, пишіть тести і лагодьте! Побачимось! 👋