Testing React Components — مقدمة React
اختبار React
Vitest + React Testing Library
في مشروع Vite نستخدم Vitest مشغّلاً للاختبارات (واجهته شبه مطابقة لـ Jest)، و jsdom لمحاكاة المتصفح، و Testing Library لاختبار المكونات كما يستخدمها المستخدم:
npm install --save-dev vitest jsdom @testing-library/react @testing-library/jest-dom// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true, // test و expect و vi بلا استيراد
setupFiles: './src/setupTests.js',
},
});// src/setupTests.js — يضيف toBeInTheDocument وأخواتها
import '@testing-library/jest-dom/vitest';أضف إلى package.json: "test": "vitest" ثم شغّل npm test. (في مشروع يستخدم Jest تكون الأسماء نفسها مع jest.fn بدل vi.fn.)
اختبار أساسي
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('renders button with text', () => {
render(<Button>اضغطني</Button>);
expect(screen.getByText('اضغطني')).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>اضغط</Button>);
fireEvent.click(screen.getByText('اضغط'));
expect(handleClick).toHaveBeenCalledTimes(1);
});اختبار async
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
test('loads and displays user', async () => {
render(<UserProfile userId="1" />);
// انتظار ظهور البيانات
await waitFor(() => {
expect(screen.getByText('أحمد')).toBeInTheDocument();
});
});
test('shows error on failure', async () => {
// Mock fetch failure
global.fetch = vi.fn(() => Promise.reject(new Error('Failed')));
render(<UserProfile userId="1" />);
await waitFor(() => {
expect(screen.getByText(/خطأ/)).toBeInTheDocument();
});
});اختبار Hooks
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});// Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('Counter', () => {
test('renders initial count', () => {
render(<Counter initialCount={5} />);
expect(screen.getByText('العدد: 5')).toBeInTheDocument();
});
test('increments count', () => {
render(<Counter initialCount={0} />);
fireEvent.click(screen.getByText('+'));
expect(screen.getByText('العدد: 1')).toBeInTheDocument();
});
test('decrements count', () => {
render(<Counter initialCount={5} />);
fireEvent.click(screen.getByText('-'));
expect(screen.getByText('العدد: 4')).toBeInTheDocument();
});
test('resets count', () => {
render(<Counter initialCount={10} />);
fireEvent.click(screen.getByText('+'));
fireEvent.click(screen.getByText('إعادة تعيين'));
expect(screen.getByText('العدد: 10')).toBeInTheDocument();
});
});اختبر فهمك