Component тест (React Testing Library)
React Testing Library (RTL) бол component-уудыг хэрэглэгчийн нүдээр тест хийдэг хэрэгсэл юм. "Хэрэглэгчийн нүдээр" гэдэг нь DOM бүтцийг биш, дэлгэцэнд юу харагдаж байгааг шалгана гэсэн үг. Энэ арга нь кодоо засварлахад тест нь эвдрэхгүй, зөвхөн үнэхээр алдаа гарсан үед л анхааруулдгаараа онцлогтой.
RTL-н үндсэн ойлголтууд
RTL-д дөрвөн үндсэн хэрэгсэл байдаг: render, screen, fireEvent, waitFor.
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
render — component-ыг виртуал DOM-д байрлуулна
screen — байрлуулсан DOM-оос элемент хайна
fireEvent — товч дарах, бичих зэрэг үйлдлийг дуурайна
waitFor — async үйлдлийн дүнг хүлээнэ
RTL-ийн шилдэг онцлог нь элемент хайх арга юм:
// Хэрэглэгч "харж" байгаа текстээр хайна — хамгийн тохиромжтой
screen.getByText("Нэвтрэх");
// Хэрэглэлтийн үүргээр хайна — товч, холбоос зэрэг
screen.getByRole("button", { name: "Илгээх" });
// Placeholder текстээр хайна — input-д тохиромжтой
screen.getByPlaceholderText("И-мэйл хаяг");
// data-testid атрибутаар хайна — зөвхөн өөр аргагүй тохиолдолд
screen.getByTestId("user-avatar");
Энгийн component тест
Байгаа CourseCard component-ыг тест хийе:
// src/components/CourseCard.tsx
interface CourseCardProps {
title: string;
lessonCount: number;
isFree: boolean;
onEnroll: () => void;
}
function CourseCard({ title, lessonCount, isFree, onEnroll }: CourseCardProps) {
return (
<div className="course-card">
<h2>{title}</h2>
<p>{lessonCount} хичээл</p>
{isFree ? (
<span className="badge">Үнэгүй</span>
) : (
<span className="badge">Pro</span>
)}
<button onClick={onEnroll}>Бүртгүүлэх</button>
</div>
);
}
export default CourseCard;
Тест файл:
// src/components/CourseCard.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import CourseCard from "./CourseCard";
describe("CourseCard component", () => {
const defaultProps = {
title: "JavaScript үндэс",
lessonCount: 20,
isFree: true,
onEnroll: vi.fn(),
};
it("курсийн нэрийг харуулна", () => {
render(<CourseCard {...defaultProps} />);
expect(screen.getByText("JavaScript үндэс")).toBeInTheDocument();
});
it("хичээлийн тоог харуулна", () => {
render(<CourseCard {...defaultProps} />);
expect(screen.getByText("20 хичээл")).toBeInTheDocument();
});
it("үнэгүй курст 'Үнэгүй' badge харуулна", () => {
render(<CourseCard {...defaultProps} isFree={true} />);
expect(screen.getByText("Үнэгүй")).toBeInTheDocument();
expect(screen.queryByText("Pro")).not.toBeInTheDocument();
});
it("Pro курст 'Pro' badge харуулна", () => {
render(<CourseCard {...defaultProps} isFree={false} />);
expect(screen.getByText("Pro")).toBeInTheDocument();
expect(screen.queryByText("Үнэгүй")).not.toBeInTheDocument();
});
it("'Бүртгүүлэх' дарахад onEnroll дуудагдана", () => {
const mockEnroll = vi.fn();
render(<CourseCard {...defaultProps} onEnroll={mockEnroll} />);
fireEvent.click(screen.getByText("Бүртгүүлэх"));
expect(mockEnroll).toHaveBeenCalledTimes(1);
});
});
screen.queryByText нь getByText-тэй адил боловч элемент олдохгүй бол алдаа биш null буцаана — элемент байхгүй гэдгийг шалгахад хэрэглэнэ.
Async component тест
Өгөгдлийг сервереэс татдаг component-ыг тест хийхэд waitFor буюу findBy хэрэглэнэ:
// src/components/UserProfile.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import UserProfile from "./UserProfile";
// fetch-ийг дуурайна — бодит сервер рүү явахгүй
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe("UserProfile component", () => {
beforeEach(() => {
mockFetch.mockReset();
});
it("ачаалж байх үед spinner харуулна", () => {
mockFetch.mockImplementation(() => new Promise(() => {})); // Хэзээ ч дуусахгүй
render(<UserProfile userId="user-123" />);
expect(screen.getByText("Ачааллаж байна...")).toBeInTheDocument();
});
it("өгөгдөл ирсний дараа нэрийг харуулна", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: "user-123", name: "Болд", xp: 250 }),
});
render(<UserProfile userId="user-123" />);
// findByText нь async — element гарч ирэхийг хүлээнэ
const nameEl = await screen.findByText("Болд");
expect(nameEl).toBeInTheDocument();
expect(screen.getByText("250 XP")).toBeInTheDocument();
});
it("алдаа гарвал мессеж харуулна", async () => {
mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
render(<UserProfile userId="user-404" />);
await waitFor(() => {
expect(screen.getByText("Хэрэглэгч олдсонгүй")).toBeInTheDocument();
});
});
});
Form тест бичих
Маягт бөглөж илгээх үйлдлийг бүрэн тест хийж болно:
// src/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import LoginForm from "./LoginForm";
describe("LoginForm", () => {
it("и-мэйл болон нууц үгийн input байна", () => {
render(<LoginForm onSubmit={vi.fn()} />);
expect(screen.getByPlaceholderText("И-мэйл")).toBeInTheDocument();
expect(screen.getByPlaceholderText("Нууц үг")).toBeInTheDocument();
});
it("маягт бөглөж илгээхэд onSubmit дуудагдана", async () => {
const mockSubmit = vi.fn();
render(<LoginForm onSubmit={mockSubmit} />);
// Input-д бичих
fireEvent.change(screen.getByPlaceholderText("И-мэйл"), {
target: { value: "test@example.com" },
});
fireEvent.change(screen.getByPlaceholderText("Нууц үг"), {
target: { value: "password123" },
});
// Товч дарах
fireEvent.click(screen.getByRole("button", { name: "Нэвтрэх" }));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
email: "test@example.com",
password: "password123",
});
});
});
it("и-мэйлгүй илгээхэд алдаа харуулна", async () => {
render(<LoginForm onSubmit={vi.fn()} />);
fireEvent.click(screen.getByRole("button", { name: "Нэвтрэх" }));
await waitFor(() => {
expect(screen.getByText("И-мэйл хаягаа оруулна уу")).toBeInTheDocument();
});
});
});
Тест бичих дадал нь кодыг зөв бичих дадалтай адил чухал ур чадвар. Эхэндээ "энэ component-д ямар зүйлийг тест хийх ёстой вэ?" гэж асуугаад хамгийн чухал зан байдлаасаа л эхэл — бүх зүйлийг нэн даруй хамрах шаардлагагүй.
Дараагийн хичээлд:
useState, useEffect, useContext зэрэг custom hook-уудыг тусдаа тест хийх — renderHook хэрэгслийн ашиглалтыг практикаар судална.