Зачем тестировать пропсы и состояние?
Если вы помните, в React пропсы и состояние — это как мозг и сердце вашего интерфейса. Они управляют тем, что видит пользователь, и как приложение реагирует на его действия:
- Пропсы — это внешний источник данных, который передаётся в компоненты.
- Состояние — это внутренний механизм управления динамическими данными внутри компонентов.
Тестирование пропсов важно, потому что это гарантирует, что компонент корректно отображает данные, которые ему передаются. С другой стороны, тестирование состояния помогает удостовериться, что компонент реагирует на действия пользователя или изменения данных, как ожидалось.
Пример приложения
Мы продолжим работать с маленьким учебным приложением — списком заметок. В нём есть возможность добавлять новые заметки и отображать их на экране. Вот наш текущий компонент:
import React, { useState } from "react";
interface Note {
id: number;
text: string;
}
interface NoteListProps {
notes: Note[];
}
export const NoteList: React.FC<NoteListProps> = ({ notes }) => {
const [inputValue, setInputValue] = useState("");
const addNote = () => {
alert(`New note added: ${inputValue}`);
setInputValue("");
};
return (
<div>
<ul>
{notes.map((note) => (
<li key={note.id}>{note.text}</li>
))}
</ul>
<input
data-testid="note-input"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button data-testid="add-note-button" onClick={addNote}>
Add Note
</button>
</div>
);
};
Тестирование пропсов
Шаг 1: проверка начальных значений пропсов
Чтобы проверить, что компонент корректно отображает переданные данные, используем render() и методы поиска из React Testing Library.
import { render, screen } from "@testing-library/react";
import { NoteList } from "./NoteList";
test("должен рендерить переданные заметки", () => {
const mockNotes = [
{ id: 1, text: "Note 1" },
{ id: 2, text: "Note 2" },
];
render(<NoteList notes={mockNotes} />);
// Проверяем, что каждая заметка отображается
mockNotes.forEach((note) => {
expect(screen.getByText(note.text)).toBeInTheDocument();
});
});
Если ваш компонент ожидает сложную структуру пропсов, создайте фикстуру (готовые тестовые данные). Это упрощает повторное использование данных в разных тестах.
Тестирование состояния
Шаг 2: проверка начального состояния
Начальное состояние — это то, где всё начинается. Проверим, что поле ввода пустое при загрузке компонента.
import { render, screen } from "@testing-library/react";
test("должен показывать пустое поле ввода по умолчанию", () => {
const mockNotes = [];
render(<NoteList notes={mockNotes} />);
const input = screen.getByTestId("note-input");
expect(input).toHaveValue(""); // Проверяем начальное состояние
});
Шаг 3: проверка изменения состояния
Пользовательский ввод и клики изменяют состояние компонента. Используем fireEvent для симуляции этих действий.
import { render, screen, fireEvent } from "@testing-library/react";
test("должен обновлять состояние при вводе текста", () => {
render(<NoteList notes={[]} />);
const input = screen.getByTestId("note-input");
fireEvent.change(input, { target: { value: "New note" } });
expect(input).toHaveValue("New note"); // Проверка обновленного состояния
});
Тестирование взаимодействия пропсов и состояния
Часто состояние зависит от пропсов или, наоборот, пропсы передаются в зависимости от состояния. Проверим, что добавление заметки очищает поле ввода:
test("должен очищать поле ввода после добавления заметки", () => {
render(<NoteList notes={[]} />);
const input = screen.getByTestId("note-input");
const button = screen.getByTestId("add-note-button");
fireEvent.change(input, { target: { value: "New note" } });
fireEvent.click(button); // Симулируем добавление заметки
expect(input).toHaveValue(""); // Поле ввода должно быть очищено
});
Ошибки, которые можно встретить
Если тесты падают, это часто связано с:
- Неправильным селектором (например,
data-testidне установлен). - Забытой симуляцией события.
- Неправильной проверкой на начальное состояние.
Как тесты упрощают жизнь разработчика?
- Стабильность: убедитесь, что данные, которые приходят в компонент через пропсы, отображаются корректно и без неожиданностей.
- Гарантия работы: тестирование состояния даёт уверенность, что вся логика работы компонента соответствует требованиям.
- Гибкость: как только вы добавите новых пользователей или функционал, тесты покажут, сломали вы что-нибудь или нет. Это особенно важно для живых приложений.
Заключительный пример: пользовательское взаимодействие
Давайте объединим все знания в один тест, который проверяет полный сценарий:
- Отобразить заметки.
- Ввести новую заметку.
- Добавить её (только в UI, поскольку реальная логика добавления не реализована).
test("полный сценарий добавления заметки", () => {
const mockNotes = [{ id: 1, text: "Note 1" }];
render(<NoteList notes={mockNotes} />);
// Проверяем начальное состояние
expect(screen.getByText("Note 1")).toBeInTheDocument();
const input = screen.getByTestId("note-input");
const button = screen.getByTestId("add-note-button");
// Симулируем добавление новой заметки
fireEvent.change(input, { target: { value: "New note" } });
fireEvent.click(button);
// Убедитесь в состоянии после добавления заметки
expect(input).toHaveValue(""); // Поле ввода должно быть очищено
expect(window.alert).toHaveBeenCalledWith("New note added: New note"); // Проверяем alert
});
// Вспомогательная настройка для подавления alert в тестах:
beforeAll(() => {
jest.spyOn(window, "alert").mockImplementation(() => {});
});
Используя эти методики, вы сможете тестировать как статические, так и динамические компоненты в React-приложениях, делая их надёжными и готовыми к выкатке даже на очень требовательные проекты!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ