Kodax

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();
  });
});
اختبر فهمك