Перейти к содержимому

18. Тестирование

Тестирование в Remix охватывает unit-тесты (Vitest), интеграционные тесты маршрутов и E2E тесты (Playwright/Cypress).

Окно терминала
npm install -D vitest @testing-library/react @testing-library/jest-dom happy-dom
vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: "happy-dom",
setupFiles: ["./tests/setup.ts"],
},
});
// tests/setup.ts
import "@testing-library/jest-dom/vitest";
app/utils/format.test.ts
import { describe, it, expect } from "vitest";
import { formatPrice, slugify } from "./format";
describe("formatPrice", () => {
it("форматирует цену в рубли", () => {
expect(formatPrice(1500)).toBe("1 500 ₽");
expect(formatPrice(99.5)).toBe("99,50 ₽");
});
});
describe("slugify", () => {
it("создаёт slug из строки", () => {
expect(slugify("Hello World")).toBe("hello-world");
expect(slugify("React & Remix")).toBe("react-remix");
});
});
// app/routes/users.$id.test.ts
import { createRequest } from "@remix-run/testing";
import { loader } from "./users.$id";
import { db } from "~/db.server";
// Мокируем БД
vi.mock("~/db.server", () => ({
db: {
user: {
findUnique: vi.fn(),
},
},
}));
describe("loader /users/:id", () => {
it("возвращает пользователя", async () => {
const mockUser = { id: "1", name: "Алексей", email: "[email protected]" };
vi.mocked(db.user.findUnique).mockResolvedValue(mockUser);
const response = await loader({
request: new Request("http://app.com/users/1"),
params: { id: "1" },
context: {},
});
const data = await response.json();
expect(data.user).toEqual(mockUser);
});
it("бросает 404 если пользователь не найден", async () => {
vi.mocked(db.user.findUnique).mockResolvedValue(null);
await expect(
loader({ request: new Request("http://app.com/users/999"), params: { id: "999" }, context: {} })
).rejects.toThrow();
});
});
app/routes/login.test.ts
import { action } from "./login";
describe("login action", () => {
it("перенаправляет после успешного входа", async () => {
const formData = new FormData();
formData.set("email", "[email protected]");
formData.set("password", "correct-password");
const response = await action({
request: new Request("http://app.com/login", {
method: "POST",
body: formData,
}),
params: {},
context: {},
});
expect(response.status).toBe(302);
expect(response.headers.get("Location")).toBe("/dashboard");
});
});
Окно терминала
npm install -D msw
tests/mocks/handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users", () => {
return HttpResponse.json([
{ id: 1, name: "Алексей" },
{ id: 2, name: "Мария" },
]);
}),
];
tests/e2e/auth.test.ts
import { test, expect } from "@playwright/test";
test("пользователь может войти в систему", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("[email protected]");
await page.getByLabel("Пароль").fill("password123");
await page.getByRole("button", { name: "Войти" }).click();
await expect(page).toHaveURL("/dashboard");
await expect(page.getByText("Добро пожаловать")).toBeVisible();
});