Уявіть, що ви написали складний інтернет-магазин на React. Ви додали нову фічу — можливість “застосувати промокод”. Ви перевірили кошик, знижка працює. Ви завантажуєте оновлення на сервер.
Через годину вам дзвонить розлючений замовник: “Кнопка ‘Реєстрація’ перестала працювати! Ніхто не може купити товар!”.
Виявляється, ваша нова логіка промокодів випадково зламала глобальний стан userStore, який використовувався при реєстрації.
Щоб уникнути таких катастроф (регресій), розробники пишуть Автоматизовані Тести — спеціальні програми (скрипти), які перевіряють вашу програму замість вас. Щоразу перед завантаженням коду на сервер, система запускає ці тести. Якщо хоч один з них “падає” (світиться червоним), код вважається нестабільним і на сервер не потрапляє.
Мета цієї лекції — розібратися з Пірамідою тестування та навчитися писати швидкі Unit-тести за допомогою Jest та масштабні E2E-тести за допомогою Cypress.
В індустрії QA прийнято ділити всі автоматизовані тести на 3 рівні (за формою піраміди — від основи до вершини):
Jest — це найпопулярніший тестовий фреймворк у світі JavaScript (створений Facebook). Він підходить для перевірки будь-якого JS коду.
Уявіть, що у нас є простий файл math.js з однією функцією:
// файл math.js
export function sum(a, b) {
return a + b;
}
Щоб протестувати його, ми створюємо поруч файл math.test.js:
// файл math.test.js
import { sum } from "./math";
// Блок describe групує тести за темою (необов'язково)
describe("Тестування математичних операцій", () => {
// Блок test (або it) - це сам тест. Перший аргумент - що ми чекаємо
test("Функція sum має правильно додавати 1 + 2", () => {
// 1. Arrange (Підготовка)
const a = 1;
const b = 2;
// 2. Act (Дія)
const result = sum(a, b);
// 3. Assert (Перевірка/Твердження)
// Ми кажемо: "Я ОЧІКУЮ (expect), що result БУДЕ ДОРІВНЮВАТИ (toBe) 3"
expect(result).toBe(3);
});
});
Якщо ви зумисне зміните функцію sum (наприклад, return a * b), коли ви запустите команду npm test, Jest покаже червоний екран: “Expected: 3, Received: 2”. Тест врятував ваш код від релізу з помилкою!
expect(value).toBe(5) — сувора рівність чисел або рядків (===).expect(obj).toEqual({x: 1}) — глибоке порівняння об’єктів або масивів за вмістом.expect(text).toMatch(/Світ/) — чи містить рядок регулярний вираз.expect(arr).toContain('Яблуко') — чи є елемент у масиві.Тестувати звичайні функції (sum, capitalizeFirstLetter) дуже легко. Але React-компонент малює складний DOM.
Для цього використовується бібліотека React Testing Library (зазвичай іде в комплекті з Jest).
Вона пропагує філософію: “Тестуйте ваш код так, як ним користується реальний користувач” (тобто, не перевіряйте стан (State) зміної напряму, а перевіряйте, що побачив юзер на екрані).
// Button.js
export function Button({ label }) {
return <button className="btn">{label}</button>;
}
// Button.test.js
import { render, screen } from "@testing-library/react";
import { Button } from "./Button";
test("Кнопка мала б рендерити переданий текст", () => {
// Віртуально малюємо компонент у пам'яті комп'ютера (без реального браузера)
render(<Button label="Зберегти профайл" />);
// Шукаємо кнопку на екрані за її текстом
const buttonElement = screen.getByText(/Зберегти профайл/i);
// Перевіряємо, що такий елемент фізично існує в DOM
expect(buttonElement).toBeInTheDocument();
});
Для тестів, де треба “клікнути” кнопку (імітувати дії), RTL надає об’єкт fireEvent або бібліотеку userEvent.
import userEvent from "@testing-library/user-event";
test("Зміна лічильника при кліку", async () => {
render(<Counter />);
const button = screen.getByRole("button", { name: "Клікни мене" });
// Імітуємо клік користувача
await userEvent.click(button);
// Очікуємо, що цифра на екрані змінилася з 0 на 1
expect(screen.getByText("Рахунок: 1")).toBeInTheDocument();
});
Модульні тести Jest — швидкі, але вони не гарантують, що кнопка не перекрита невидимим div-ом у реальному браузері, або що сервер не впав з помилкою 500.
Е2Е тести перевіряють весь потік від початку до кінця. Сьогодні лідером у цьому сегменті є Cypress (або його конкурент Playwright).
Cypress запускає повноцінний браузер (Chrome) і керує ним через код. Ви можете дивитися відео або робити скріншоти процесу.
// cypress/e2e/login.cy.js
describe("Форма Авторизації", () => {
it("Користувач може успішно залогінитись", () => {
// 1. Справжній браузер переходить на вашу сторінку
cy.visit("http://localhost:3000/login");
// 2. Робот знаходить input за атрибутом id або класом і друкує текст
cy.get("#email").type("user@test.com");
cy.get('input[name="password"]').type("123456");
// 3. Знаходить кнопку і вдаряє по ній лівим кліком миші
cy.get('button[type="submit"]').click();
// 4. Перевірка: чи змінився URL в адресному рядку браузера на /dashboard
cy.url().should("include", "/dashboard");
// 5. Перевірка: чи з'явилося привітання після редиректу
cy.contains("Ласкаво просимо, User!").should("be.visible");
});
});
Cypress чудовим тим, що він автоматично “чекає” (Auto-waiting). Вам не потрібно писати setTimeout(), чекаючи поки React завантажить дані після кліку. Якщо елемент не з’явився одразу, Cypress почекає 4 секунди, перевіряючи DOM кожні 10 мілісекунд, перш ніж здатися і видати помилку тесту.
TDD — це інженерна методологія, яка перевертає процес програмування з ніг на голову.
Класичний підхід: Написати код -> Написати тести на цей код. Підхід TDD: Написати тести -> Зрозуміти, що код не працює (Red) -> Написати код -> Тест пройдено (Green) -> Покращити якість коду (Refactor).
Цикл TDD (Червоний - Зелений - Рефакторинг):
TDD змушує думати про “Архітектуру та Інтерфейси” до написання самої реалізації і гарантує 100% покриття коду тестами (Code Coverage).
describe, test, expect(x).toBe(y)).useState, а те, що по бачить користувач на екрані. Ми шукаємо текст і кнопки віртуальному DOM (screen.getByText).cy.visit()), вводить дані в поля форми та перевіряє візуальний результат сторінки.expect(someVar).toBe(5). Який аналог цієї функції буде використано для порівняння двох складних об’єктів з ключами (toEqual)?getByText, а не за ім’ям CSS класу (наприклад .login-button)?input.