Тема: Написання тестів. Покриття основних функцій додатку (наприклад, додавання коментаря або зміна профілю) юніт-тестами.
Мета: Опанувати основи автоматизованого тестування React-додатків; налаштувати тестове середовище за допомогою Vitest та React Testing Library; навчитися писати модульні (Unit) тести для ізольованих UI-компонентів, стимулювати дії користувача (кліки, введення тексту) та ізолювати мережеві запити за допомогою техніки мокінгу (Mocking).
Технологічний стек: React, Vite, TypeScript, Vitest, React Testing Library (RTL), @testing-library/user-event, jsdom.
Button або PostCard).user-event.NewsFeed з Лаб №6), замокавши (Mock) кастомний хук useFetch або бібліотеку axios.Оскільки наш проєкт побудовано на Vite, використання Jest буде вимагати складного налаштування трансформацій. Офіційна рекомендація екосистеми Vite — це Vitest, який має абсолютно ідентичний до Jest синтаксис (API), але працює “з коробки”.
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
vite.config.ts, щоб додати конфігурацію тестування:/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
globals: true, // Використовувати describe, it, expect глобально
environment: "jsdom", // Імітація DOM дерева
setupFiles: "./setupTests.ts", // Файл для ініціалізації jest-dom
},
});
setupTests.ts у корені проєкту:// Імпорт додаткових матчерів (наприклад, toBeInTheDocument)
import "@testing-library/jest-dom";
package.json:"scripts": {
// ... інші скрипти
"test": "vitest run"
}
Створіть файл поряд з вашим компонентом, наприклад src/components/Button/Button.test.tsx.
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import Button from "./Button"; // Ваш компонент з Лаб №1
describe("Компонент Button", () => {
it("повинен відмалюватися з правильним текстом", () => {
// 1. Рендеримо компонент
render(<Button>Натисни мене</Button>);
// 2. Шукаємо елемент за його роллю на екрані
const buttonElement = screen.getByRole("button", { name: /натисни мене/i });
// 3. Здійснюємо перевірку (Assertion)
expect(buttonElement).toBeInTheDocument();
});
it("повинен бути вимкненим (disabled), якщо передано відповідний проп", () => {
render(<Button disabled>Зберегти</Button>);
const buttonElement = screen.getByRole("button");
expect(buttonElement).toBeDisabled();
});
});
Запустіть npm run test, щоб переконатися, що тести успішно пройдено.
Додамо тест для компонента Login (з Лаб №5), який перевіряє поведінку форми та імітує клавіатурний ввід.
Створіть src/pages/Login/Login.test.tsx:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import { BrowserRouter } from "react-router-dom"; // Щоб не падав useNavigate
import Login from "./Login";
// Мок для AuthContext
import { AuthContext } from "../../context/AuthContext";
describe("Сторінка Login", () => {
it("дозволяє користувачу ввести email та відправити форму", async () => {
// Створюємо "фейкову" функцію login для перехоплення виклику
const mockLogin = vi.fn();
// Ініціалізуємо імітатор дій користувача
const user = userEvent.setup();
render(
<BrowserRouter>
<AuthContext.Provider
value=
>
<Login />
</AuthContext.Provider>
</BrowserRouter>,
);
// Шукаємо інпут (бажано за label через getByLabelText, або за Placeholder)
const emailInput = screen.getByPlaceholderText(/email/i);
const submitButton = screen.getByRole("button", { name: /увійти/i });
// Симулюємо ввід (друкування на клавіатурі)
await user.type(emailInput, "test@kpi.ua");
// Перевіряємо чи оновилось значення в полі
expect(emailInput).toHaveValue("test@kpi.ua");
// Симулюємо клік
await user.click(submitButton);
// Перевіряємо, чи наша "фейкова" функція була викликана з правильним параметром!
expect(mockLogin).toHaveBeenCalledWith("test@kpi.ua");
});
});
Щоб протестувати NewsFeed.tsx (з Лаб №6), нам потрібно заборонити компоненту робити реальний запит на JSONPlaceholder. Ми “підмінимо” (замокаємо) сам хук useFetch.
Створіть src/components/NewsFeed/NewsFeed.test.tsx:
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import NewsFeed from "./NewsFeed";
import * as useFetchModule from "../../hooks/useFetch"; // Імпорт всього модуля
// Кажемо Vitest перехопити цей модуль
vi.mock("../../hooks/useFetch");
describe("Стрічка Новин", () => {
it("показує індикатор завантаження під час отримання даних", () => {
// Налаштовуємо мок: що саме поверне хук ПРИ ЦЬОМУ запуску
// @ts-ignore (ігноруємо типізацію замоканих функцій для простоти)
useFetchModule.useFetch.mockReturnValue({
data: null,
isLoading: true,
error: null,
});
render(<NewsFeed />);
expect(screen.getByText(/завантаження/i)).toBeInTheDocument();
});
it("успішно рендерить список статей", () => {
const mockPosts = [
{ id: 1, title: "Перша стаття", body: "Текст 1" },
{ id: 2, title: "Друга стаття", body: "Текст 2" },
];
// Змінюємо поведінку моку для ЦЬОГО конкретного тесту
// @ts-ignore
useFetchModule.useFetch.mockReturnValue({
data: mockPosts,
isLoading: false,
error: null,
});
render(<NewsFeed />);
// Перевіряємо чи відрендерилась кількість статей
const headings = screen.getAllByRole("heading", { level: 3 });
expect(headings).toHaveLength(2);
expect(headings[0]).toHaveTextContent("Перша стаття");
});
});
jsdom) та реальним браузером? Чого не вистачає у jsdom (наприклад, щодо стилізації або ширини вікна)?@testing-library/user-event (метод .type()), ніж базовий fireEvent.change()?Login ми змушені були “огорнути” його у фіктивні компоненти <BrowserRouter> та <AuthContext.Provider>? Що сталося б без цих обгорток?npm run test (Vitest) успішно проходить (“green tests”) і покриває як мінімум один UI-компонент, одну форму та один запит.lab_08.md винести фрагменти коду:
lab_08.md дати вичерпні відповіді на 5 контрольних запитань.