الاختبار 8 دقيقة للقراءة 1,119 مشاهدات

اختبار React في 2026: أمثلة على اختبارات الوحدة والتكامل والشامل

أمثلة عملية لاختبار تطبيقات React في 2026 — Vitest و React Testing Library لاختبارات الوحدة والتكامل، و Playwright و Cypress للاختبار الشامل، مع تقسيم الاختبارات في CI وأسباب الاختبارات المتذبذبة.

Software testing

تفشل معظم مجموعات اختبار React لسببين متكررين: أنها تختبر تفاصيل التنفيذ التي تنكسر مع كل إعادة هيكلة، وأن اختباراتها الشاملة متذبذبة لدرجة تجعل الفريق يفقد الثقة بها. كلا السببين يمكن تفاديه.

هذا الدليل قائم على أمثلة عملية لا على النظريات. الإصدارات المستخدمة وقت الكتابة: Vitest 4.1 وReact Testing Library 16.3 وPlaywright 1.62 وCypress 15.20.

الطبقات الثلاث ودور كل منها

الطبقةما تختبرهالأدواتالسرعةالعدد
الوحدةدالة أو hook بمعزل عن غيرهVitest أو Jestأجزاء من الثانيةكثيرة
التكاملمكوّن مع أبنائه وحالته الحقيقيةVitest مع Testing Libraryسريعةمعظم اختباراتك
الشاملمتصفح حقيقي مقابل التطبيق العاملPlaywright أو Cypressثوانٍقليلة، للمسارات الحرجة

الصف الأوسط هو موضع القيمة الحقيقية. اختبارات التكامل تعرض مكوّنات حقيقية بحالة حقيقية وتتحقق مما يراه المستخدم، فتبقى صامدة أمام إعادة الهيكلة وتلتقط الأخطاء الفعلية. إذا كنت ستكتب نوعًا واحدًا فقط من الاختبارات، فاكتب هذا النوع.

الإعداد: Vitest و Testing Library

npm i -D vitest @vitejs/plugin-react jsdom \
  @testing-library/react @testing-library/user-event @testing-library/jest-dom
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/test/setup.ts',
  },
})

مثال على اختبار وحدة

المنطق الخالص لا يحتاج إلى عرض المكوّن. اختبر الدالة نفسها، لا المكوّن الذي يستدعيها.

// src/lib/cart.test.ts
import { describe, it, expect } from 'vitest'
import { cartTotal } from './cart'

describe('cartTotal', () => {
  it('يجمع السعر مضروبًا في الكمية', () => {
    const items = [
      { price: 10.5, qty: 2 },
      { price: 3.25, qty: 4 },
    ]
    expect(cartTotal(items)).toBe(34)
  })

  it('يعيد صفرًا لسلة فارغة', () => {
    expect(cartTotal([])).toBe(0)
  })
})

مثال على اختبار تكامل

القاعدة الأهم هنا: ابحث عن العناصر كما يفعل المستخدم. استخدم الأدوار والتسميات، لا معرّفات الاختبار أو أسماء الأصناف. الاختبار الذي يجد الزر عبر اسمه الوصفي يستمر في العمل بعد إعادة الهيكلة، ويفشل إذا أصبح الزر غير قابل للوصول — وهو خطأ يستحق الالتقاط.

// src/components/LoginForm.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { LoginForm } from './LoginForm'

describe('LoginForm', () => {
  it('يرسل البيانات المُدخلة', async () => {
    const user = userEvent.setup()
    const onSubmit = vi.fn()

    render(<LoginForm onSubmit={onSubmit} />)

    await user.type(screen.getByLabelText(/email/i), 'dev@example.com')
    await user.type(screen.getByLabelText(/password/i), 'hunter2')
    await user.click(screen.getByRole('button', { name: /sign in/i }))

    expect(onSubmit).toHaveBeenCalledWith({
      email: 'dev@example.com',
      password: 'hunter2',
    })
  })
})

لاحظ استخدام findByRole عند انتظار عنصر غير متزامن. فـ getBy يرمي خطأً فورًا، بينما findBy يعيد المحاولة حتى يظهر العنصر — وهذا وحده يلغي معظم فترات الانتظار العشوائية التي يضيفها المطورون.

محاكاة الشبكة عبر MSW

لا تحاكِ fetch مباشرة. اعترض الطلب على مستوى الشبكة ليعمل المكوّن بشيفرة جلب البيانات الحقيقية.

// src/test/server.ts
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'

export const server = setupServer(
  http.get('/api/projects', () => {
    return HttpResponse.json([
      { id: 1, name: 'Apollo' },
      { id: 2, name: 'Borealis' },
    ])
  }),
)

مثال على اختبار شامل: Playwright

// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test'

test('يستطيع المستخدم تسجيل الدخول وإتمام الطلب', async ({ page }) => {
  await page.goto('/login')

  await page.getByLabel('Email').fill('dev@example.com')
  await page.getByLabel('Password').fill('hunter2')
  await page.getByRole('button', { name: 'Sign in' }).click()

  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()

  await page.getByRole('link', { name: 'Cart' }).click()
  await page.getByRole('button', { name: 'Checkout' }).click()

  await expect(page.getByText('Order confirmed')).toBeVisible()
  await expect(page).toHaveURL(/\/orders\/\d+/)
})

ينتظر Playwright ظهور العنصر واستقراره وتفعيله قبل التعامل معه، لذا نادرًا ما تحتاج إلى فترات انتظار صريحة. وإعداد trace: 'on-first-retry' هو الأنفع على الإطلاق: عند فشل اختبار في CI تحصل على تسجيل كامل للتنفيذ بدل رسالة خطأ مجردة.

مثال على اختبار شامل: Cypress

// cypress/e2e/checkout.cy.ts
describe('checkout', () => {
  it('يتيح للمستخدم المسجّل إتمام الطلب', () => {
    cy.visit('/login')

    cy.findByLabelText(/email/i).type('dev@example.com')
    cy.findByLabelText(/password/i).type('hunter2')
    cy.findByRole('button', { name: /sign in/i }).click()

    cy.findByRole('heading', { name: /dashboard/i }).should('be.visible')
    cy.contains('Order confirmed').should('be.visible')
  })
})

Playwright أم Cypress؟

Playwright 1.62Cypress 15.20
المتصفحاتChromium و Firefox و WebKitChromium و Firefox و WebKit
التوازيمدمج ومجانيمدمج، مع لوحة للتنسيق
تقسيم CIبخيار واحدعبر Cypress Cloud أو يدويًا
التنقيحعارض التتبع بعد التنفيذمشغّل تفاعلي بالسفر عبر الزمن
تعدد التبويبات والنطاقاتمدعوممحدود
المقارنة البصريةمدمجةعبر إضافة

اختر Playwright للسرعة وتغطية المتصفحات والتوسع في CI — فهو الخيار الافتراضي الأقوى في 2026. واختر Cypress إذا كان فريقك يقدّر المشغّل التفاعلي، فهو ما يزال أفضل تجربة تنقيح محلية.

ولا تستخدم الاثنين معًا: مجموعتان من الاختبارات الشاملة تعنيان مجموعتين من التذبذب دون مسؤول واضح عن أي منهما.

تشغيل الاختبارات الشاملة في CI

# .github/workflows/e2e.yml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --shard=${{ matrix.shard }}/4

أربعة أقسام تحوّل مجموعة تستغرق اثنتي عشرة دقيقة إلى ثلاث تقريبًا. ورفع التقرير عند الفشل أهم مما يبدو: بدونه يصعب جدًا تشخيص فشل يحدث في CI فقط.

لماذا تتذبذب الاختبارات

  • فترات انتظار ثابتة. waitForTimeout(2000) مجرد تخمين: طويل على جهاز سريع وقصير على خادم CI محمّل. انتظر شرطًا بدل مدة.
  • حالة مشتركة بين الاختبارات. إذا نجح الاختبار «ب» فقط بعد تشغيل «أ»، فهما اختبار واحد. هيّئ لكل اختبار حالته.
  • استدعاءات خارجية حقيقية. واجهة برمجية خارجية داخل اختبار شامل تعني أن تعطّل طرف آخر يُفشل بناءك. اعترضها على مستوى الشبكة.
  • محددات مرتبطة بالتنسيق. .btn-primary-2 ينكسر مع أول إعادة تصميم، بخلاف الأدوار والتسميات.
  • الوقت والعشوائية. ثبّت الساعة وابذر مولّد العشوائية، وإلا فشل الاختبار عند منتصف الليل أو مرة كل مئة تشغيل.

ما لا ينبغي اختباره

لا تتحقق من الحالة الداخلية أو أسماء الخصائص أو عدد مرات إعادة العرض — فذلك اختبار لـ React نفسه لا لتطبيقك. وتجنّب لقطات snapshot لشجرات مكوّنات كبيرة، فهي تُعاد كتابتها دون أن يقرأها أحد.

اسأل نفسك قبل كتابة أي اختبار: إذا فشل هذا الاختبار، هل سيخبرني بأن شيئًا معطّل لدى المستخدم؟ إن كانت الإجابة لا، فهو تكلفة صيانة دون فائدة أمان.

أسئلة شائعة

ما مثال اختبار React الشامل الجيد؟

رحلة مستخدم حرجة واحدة تُنفَّذ في متصفح حقيقي — تسجيل دخول، إضافة إلى السلة، إتمام طلب — مع التحقق مما يراه المستخدم فقط. مثال Playwright أعلاه نسخة كاملة قابلة للتشغيل.

هل أستخدم Playwright أم Cypress مع React في 2026؟

‏Playwright هو الخيار الافتراضي الأقوى: أسرع، ويدعم المتصفحات فعليًا، مع توازٍ مجاني وتقسيم في CI بخيار واحد. ويبقى Cypress ممتازًا إذا كان فريقك يقدّر مشغّله التفاعلي. اختر واحدًا فقط.

هل Vitest أفضل من Jest لمشاريع React؟

لمشاريع Vite نعم، لأنه يعيد استخدام إعدادات Vite ويبدأ أسرع ويحتاج إعدادًا أقل. أما إذا كانت لديك مجموعة Jest تعمل جيدًا فالانتقال اختياري، فـ Jest 30 ما يزال مدعومًا بنشاط.

كم اختبارًا شاملًا ينبغي أن أكتب؟

قليلًا. غطِّ الرحلات التي تسبب خسارة أو تعطّل المستخدمين عند انكسارها: المصادقة، الدفع، النشر. وما عدا ذلك مكانه اختبارات التكامل، فهي أسرع وأقل تذبذبًا.

لماذا تنكسر اختباراتي مع كل إعادة هيكلة؟

لأنها تبحث عن تفاصيل التنفيذ: معرّفات اختبار وأسماء أصناف ومكوّنات داخلية. ابحث بالأدوار والتسميات، فتصمد الاختبارات أمام إعادة الهيكلة وتظل تلتقط الأعطال الحقيقية.

كيف أختبر مكوّنًا يجلب بيانات؟

اعترض الطلب على مستوى الشبكة عبر MSW بدل محاكاة fetch، فيعمل المكوّن بشيفرته الحقيقية وتختبر ما يُنشر فعلًا.

كيف أوقف تذبذب الاختبارات الشاملة؟

احذف فترات الانتظار الثابتة، واعزل الحالة بين الاختبارات، واعترض الاستدعاءات الخارجية، واستخدم محددات قائمة على الأدوار، وثبّت الوقت والعشوائية. هذه الخمس تغطي الغالبية العظمى من حالات التذبذب.

ما نسبة التغطية التي ينبغي استهدافها؟

لا رقم محدد. التغطية تُظهر أي الأسطر نُفِّذت، لا ما إذا كان السلوك صحيحًا. مجموعة بنسبة 60% تغطي رحلات مستخدم حقيقية أفضل من أخرى بنسبة 95% تتحقق من تفاصيل داخلية.

مشاركة هذه المقالة:
ES
كتبه

Edrees Salih

مهندس برمجيات متكامل يتمتع بخبرة 9 سنوات. شغوف ببناء حلول قابلة للتطوير ومشاركة المعرفة مع مجتمع المطورين.

عرض الملف الشخصي

التعليقات (0)

اترك تعليقًا

لن يتم نشر بريدك الإلكتروني.

لا توجد تعليقات بعد. كن أول من يشارك أفكاره!

مقالات ذات صلة

مقالات ذات صلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.