Основы тестирования
Тест — код, который запускает часть приложения и проверяет результат. В режиме фреймворка удобно, что loader и action — обычные функции: им можно передать запрос и посмотреть, что они вернули. Компоненты, которым нужен роутер, проверяют с помощью createRoutesStub — маленького тестового роутера.
Установка
npm install -D vitest jsdom @testing-library/reactVitest — тестовый фреймворк для проектов на Vite, jsdom имитирует браузер, Testing Library отрисовывает компоненты. Добавьте в package.json скрипт "test": "vitest".
Плагин reactRouter() из vite.config.ts в тестах не нужен, поэтому создайте отдельный файл настроек:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: { environment: "jsdom" },
});Тест загрузчика
Тесты кладут рядом с кодом в файлы *.test.ts. Проверим, что загрузчик статьи выбрасывает 404 для несуществующего id:
// app/routes/post.test.ts
import { describe, expect, it } from "vitest";
import { loader } from "./post";
import type { Route } from "./+types/post";
describe("loader статьи", () => {
it("возвращает 404 для несуществующей статьи", async () => {
const args = {
request: new Request("http://localhost/posts/999"),
params: { postId: "999" },
context: {},
} as Route.LoaderArgs;
const error = await loader(args).catch((e: unknown) => e);
expect(error).toBeInstanceOf(Response);
expect((error as Response).status).toBe(404);
});
});Команда npm test запустит тест, и Vitest напишет в терминале, что он прошёл. Приведение as Route.LoaderArgs нужно потому, что у настоящих аргументов есть ещё служебные поля.
Тест компонента
createRoutesStub создаёт роутер из описаний маршрутов. Загрузчик в нём можно заменить заглушкой:
// app/routes/home.test.tsx
import { render, screen } from "@testing-library/react";
import { createRoutesStub } from "react-router";
import { expect, it } from "vitest";
import Home from "./home";
it("показывает список статей", async () => {
const Stub = createRoutesStub([
{
path: "/",
Component: Home,
loader: () => ({ posts: [{ id: 1, title: "Привет, мир", body: "" }] }),
},
]);
render(<Stub initialEntries={["/"]} />);
expect(await screen.findByText("Привет, мир")).toBeTruthy();
});Тест отрисует главную с одной статьёй и найдёт её заголовок. findByText ждёт, пока загрузчик-заглушка отработает. Если компонент ругается на тип пропсов, оберните его: Component: () => ... с хуком useLoaderData.
Что тестировать
| Что | Чем |
|---|---|
Проверку формы в action | Vitest, вызов функции |
Перенаправление гостя в loader | Vitest, проверка выброшенного ответа |
| Вывод данных и ошибок в компоненте | createRoutesStub и Testing Library |
| Весь путь пользователя в браузере | Сквозные тесты, например Playwright |
Частая ошибка
Тестировать загрузчик, который работает с настоящей базой blog.db. Тест добавит или удалит записи, и результат будет зависеть от того, что лежало в базе. Для тестов используйте отдельную базу (через переменную окружения DATABASE_PATH) или подменяйте функции модуля данных.