الاختبار

أمثلة

تعلم كيفية إعداد Next.js مع أدوات الاختبار الشائعة: Cypress، Playwright، وJest مع مكتبة React Testing Library.

Cypress

Cypress هو مشغل اختبار يُستخدم لـاختبار النهاية إلى النهاية (E2E) واختبار المكونات.

البدء السريع

يمكنك استخدام create-next-app مع مثال with-cypress للبدء بسرعة.

Terminal
npx create-next-app@latest --example with-cypress with-cypress-app

الإعداد اليدوي

للبدء مع Cypress، قم بتثبيت حزمة cypress:

Terminal
npm install --save-dev cypress

أضف Cypress إلى حقل scripts في package.json:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "cypress:open": "cypress open"
  }
}

شغّل Cypress لأول مرة لإنشاء أمثلة تستخدم هيكل المجلدات الموصى به:

Terminal
npm run cypress:open

يمكنك الاطلاع على الأمثلة المُنشأة وقسم كتابة أول اختبار لك في وثائق Cypress للتعرف على كيفية استخدامه.

هل يجب استخدام اختبارات E2E أم اختبارات المكونات؟

تحتوي وثائق Cypress على دليل يوضح الفرق بين هذين النوعين من الاختبارات ومتى يكون كل منهما مناسبًا للاستخدام.

إنشاء أول اختبار E2E باستخدام Cypress

بافتراض وجود صفحتي Next.js التاليتين:

pages/index.js
import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <h1>الصفحة الرئيسية</h1>
      <Link href="/about">حول</Link>
    </nav>
  )
}
pages/about.js
export default function About() {
  return (
    <div>
      <h1>صفحة حول</h1>
      <Link href="/">الصفحة الرئيسية</Link>
    </div>
  )
}

أضف اختبارًا للتحقق من عمل التنقل بشكل صحيح:

cypress/e2e/app.cy.js
describe('التنقل', () => {
  it('يجب الانتقال إلى صفحة حول', () => {
    // ابدأ من الصفحة الرئيسية
    cy.visit('http://localhost:3000/')

    // ابحث عن رابط يحتوي على "about" وانقر عليه
    cy.get('a[href*="about"]').click()

    // يجب أن يتضمن الرابط الجديد "/about"
    cy.url().should('include', '/about')

    // يجب أن تحتوي الصفحة الجديدة على h1 بعنوان "صفحة حول"
    cy.get('h1').contains('صفحة حول')
  })
})

يمكنك استخدام cy.visit("/") بدلاً من cy.visit("http://localhost:3000/") إذا أضفت baseUrl: 'http://localhost:3000' إلى ملف تكوين cypress.config.js.

إنشاء أول اختبار مكونات باستخدام Cypress

تقوم اختبارات المكونات ببناء وتثبيت مكون معين دون الحاجة إلى تجميع التطبيق بالكامل أو تشغيل خادم. هذا يسمح باختبارات أكثر كفاءة مع توفير نفس واجهة برمجة التطبيقات المستخدمة في اختبارات E2E.

معلومة مفيدة: نظرًا لأن اختبارات المكونات لا تشغل خادم Next.js، فإن ميزات مثل <Image /> وgetServerSideProps التي تعتمد على وجود خادم لن تعمل افتراضيًا. راجع وثائق Cypress لـ Next.js للحصول على أمثلة حول جعل هذه الميزات تعمل في اختبارات المكونات.

بافتراض نفس المكونات من القسم السابق، أضف اختبارًا للتحقق من عرض المكون للنتائج المتوقعة:

pages/about.cy.js
import AboutPage from './about.js'

describe('<AboutPage />', () => {
  it('يجب عرض المحتوى المتوقع', () => {
    // ثبّت مكون React لصفحة حول
    cy.mount(<AboutPage />)

    // يجب أن تحتوي الصفحة الجديدة على h1 بعنوان "صفحة حول"
    cy.get('h1').contains('صفحة حول')

    // تحقق من وجود رابط بالمسار المتوقع
    // *اتباع* الرابط أكثر ملاءمة لاختبار E2E
    cy.get('a[href="/"]').should('be.visible')
  })
})

تشغيل اختبارات Cypress

اختبارات E2E

نظرًا لأن اختبارات E2E في Cypress تختبر تطبيق Next.js حقيقي، فإنها تتطلب تشغيل خادم Next.js قبل بدء Cypress. نوصي بتشغيل الاختبارات ضد كود الإنتاج لمحاكاة سلوك التطبيق الفعلي.

قم بتشغيل npm run build ثم npm run start، ثم شغّل npm run cypress -- --e2e في نافذة طرفية أخرى لبدء Cypress وتشغيل مجموعة اختبارات E2E.

معلومة مفيدة: بدلاً من ذلك، يمكنك تثبيت حزمة start-server-and-test وإضافتها إلى حقل scripts في package.json: "test": "start-server-and-test start http://localhost:3000 cypress" لبدء خادم إنتاج Next.js بالتزامن مع Cypress. تذكر إعادة بناء التطبيق بعد أي تغييرات جديدة.

اختبارات المكونات

شغّل npm run cypress -- --component لبدء Cypress وتنفيذ مجموعة اختبارات المكونات.

التحضير للتكامل المستمر (CI)

ستلاحظ أن تشغيل Cypress حتى الآن فتح متصفحًا تفاعليًا وهو غير مثالي لبيئات CI. يمكنك أيضًا تشغيل Cypress بدون واجهة مرئية باستخدام أمر cypress run:

package.json
{
  "scripts": {
    //...
    "e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
    "e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
    "component": "cypress open --component",
    "component:headless": "cypress run --component"
  }
}

يمكنك معرفة المزيد عن Cypress والتكامل المستمر من هذه المصادر:

Playwright

Playwright هو إطار اختبار يتيح لك أتمتة Chromium وFirefox وWebKit باستخدام واجهة برمجة تطبيقات واحدة. يمكنك استخدامه لكتابة اختبارات النهاية إلى النهاية (E2E) والتكامل عبر جميع المنصات.

البدء السريع

أسرع طريقة للبدء هي استخدام create-next-app مع مثال with-playwright. سيؤدي هذا إلى إنشاء مشروع Next.js مكتمل مع إعداد Playwright بالكامل.

Terminal
npx create-next-app@latest --example with-playwright with-playwright-app

الإعداد اليدوي

يمكنك أيضًا استخدام npm init playwright لإضافة Playwright إلى مشروع NPM موجود.

للبدء يدويًا مع Playwright، قم بتثبيت حزمة @playwright/test:

Terminal
npm install --save-dev @playwright/test

أضف Playwright إلى حقل scripts في package.json:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test:e2e": "playwright test"
  }
}

إنشاء أول اختبار E2E باستخدام Playwright

بافتراض وجود صفحتي Next.js التاليتين:

pages/index.js
import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <Link href="/about">حول</Link>
    </nav>
  )
}
pages/about.js
export default function About() {
  return (
    <div>
      <h1>صفحة حول</h1>
    </div>
  )
}

أضف اختبارًا للتحقق من عمل التنقل بشكل صحيح:

import { test, expect } from '@playwright/test'

test('يجب الانتقال إلى صفحة حول', async ({ page }) => {
  // ابدأ من الصفحة الرئيسية (يتم تعيين baseURL عبر webServer في ملف playwright.config.ts)
  await page.goto('http://localhost:3000/')
  // ابحث عن عنصر يحتوي على نص 'حول' وانقر عليه
  await page.click('text=حول')
  // يجب أن يكون الرابط الجديد "/about" (يتم استخدام baseURL هنا)
  await expect(page).toHaveURL('http://localhost:3000/about')
  // يجب أن تحتوي الصفحة الجديدة على h1 بعنوان "صفحة حول"
  await expect(page.locator('h1')).toContainText('صفحة حول')
})
import { test, expect } from '@playwright/test'

test('يجب الانتقال إلى صفحة حول', async ({ page }) => {
  // ابدأ من الصفحة الرئيسية (يتم تعيين baseURL عبر webServer في ملف playwright.config.ts)
  await page.goto('http://localhost:3000/')
  // ابحث عن عنصر يحتوي على نص 'حول' وانقر عليه
  await page.click('text=حول')
  // يجب أن يكون الرابط الجديد "/about" (يتم استخدام baseURL هنا)
  await expect(page).toHaveURL('http://localhost:3000/about')
  // يجب أن تحتوي الصفحة الجديدة على h1 بعنوان "صفحة حول"
  await expect(page.locator('h1')).toContainText('صفحة حول')
})

يمكنك استخدام page.goto("/") بدلاً من page.goto("http://localhost:3000/") إذا أضفت "baseURL": "http://localhost:3000" إلى ملف تكوين playwright.config.ts.

تشغيل اختبارات Playwright

نظرًا لأن Playwright يختبر تطبيق Next.js حقيقي، فإنه يتطلب تشغيل خادم Next.js قبل بدء Playwright. يُنصح بتشغيل الاختبارات ضد كود الإنتاج لمحاكاة سلوك التطبيق الفعلي.

قم بتشغيل npm run build ثم npm run start، ثم شغّل npm run test:e2e في نافذة طرفية أخرى لتشغيل اختبارات Playwright.

معلومة مفيدة: بدلاً من ذلك، يمكنك استخدام ميزة webServer للسماح لـ Playwright ببدء خادم التطوير والانتظار حتى يصبح جاهزًا بالكامل.

تشغيل Playwright على التكامل المستمر (CI)

سيقوم Playwright افتراضيًا بتشغيل اختباراتك في وضع headless. لتثبيت جميع تبعيات Playwright، شغّل npx playwright install-deps.

يمكنك معرفة المزيد عن Playwright والتكامل المستمر من هذه المصادر:

Jest ومكتبة React Testing Library

يتم استخدام Jest ومكتبة React Testing Library معًا بشكل متكرر لـاختبار الوحدة. هناك ثلاث طرق يمكنك من خلالها البدء باستخدام Jest داخل تطبيق Next.js الخاص بك:

  1. استخدام أحد أمثلة البدء السريع
  2. مع مُجمّع Rust لـ Next.js
  3. مع Babel

ستغطي الأقسام التالية كيفية إعداد Jest مع كل من هذه الخيارات:

البدء السريع

يمكنك استخدام create-next-app مع مثال with-jest للبدء بسرعة مع Jest ومكتبة React Testing Library:

Terminal
npx create-next-app@latest --example with-jest with-jest-app

إعداد Jest (مع مُجمّع Rust)

منذ إصدار Next.js 12، أصبح لدى Next.js الآن تكوين مدمج لـ Jest.

لإعداد Jest، قم بتثبيت jest، jest-environment-jsdom، @testing-library/react، @testing-library/jest-dom:

Terminal
npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

قم بإنشاء ملف jest.config.mjs في الدليل الجذري لمشروعك وأضف ما يلي:

jest.config.mjs
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  // حدد مسار تطبيق Next.js لتحميل next.config.js وملفات .env في بيئة الاختبار
  dir: './',
})

// أضف أي تكوين مخصص لتمريره إلى Jest
/** @type {import('jest').Config} */
const config = {
  // أضف خيارات إعداد إضافية قبل تشغيل كل اختبار
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],

  testEnvironment: 'jest-environment-jsdom',
}

// يتم تصدير createJestConfig بهذه الطريقة لضمان أن next/jest يمكنه تحميل تكوين Next.js غير المتزامن
export default createJestConfig(config)

تحت الغطاء، يقوم next/jest بتكوين Jest تلقائيًا لك، بما في ذلك:

  • إعداد transform باستخدام SWC
  • المحاكاة التلقائية لملفات الأنماط (.css، .module.css، ومتغيرات scss الخاصة بها)، واستيراد الصور وnext/font
  • تحميل .env (وجميع المتغيرات) إلى process.env
  • تجاهل node_modules من تحويلات الاختبار
  • تجاهل .next من تحويلات الاختبار
  • تحميل next.config.js للعلامات التي تمكّن تحويلات SWC

معلومة مفيدة: لاختبار متغيرات البيئة مباشرة، قم بتحميلها يدويًا في سيناريو إعداد منفصل أو في ملف jest.config.js. لمزيد من المعلومات، راجع متغيرات بيئة الاختبار.

إعداد جيست (مع Babel)

إذا اخترت عدم استخدام مُصرِّف Rust، فستحتاج إلى تكوين جيست يدويًا وتثبيت babel-jest و identity-obj-proxy بالإضافة إلى الحزم المذكورة أعلاه.

إليك الخيارات الموصى بها لتكوين جيست لـ Next.js:

jest.config.js
module.exports = {
  collectCoverage: true,
  // في node 14.x، موفر التغطية v8 يوفر سرعة جيدة وتقريرًا جيدًا إلى حد ما
  coverageProvider: 'v8',
  collectCoverageFrom: [
    '**/*.{js,jsx,ts,tsx}',
    '!**/*.d.ts',
    '!**/node_modules/**',
    '!<rootDir>/out/**',
    '!<rootDir>/.next/**',
    '!<rootDir>/*.config.js',
    '!<rootDir>/coverage/**',
  ],
  moduleNameMapper: {
    // التعامل مع استيراد ملفات CSS (مع وحدات CSS)
    // https://jestjs.io/docs/webpack#mocking-css-modules
    '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

    // التعامل مع استيراد ملفات CSS (بدون وحدات CSS)
    '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',

    // التعامل مع استيراد الصور
    // https://jestjs.io/docs/webpack#handling-static-assets
    '^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`,

    // التعامل مع أسماء الوحدات المختصرة
    '^@/components/(.*)$': '<rootDir>/components/$1',
  },
  // إضافة المزيد من خيارات الإعداد قبل تشغيل كل اختبار
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
  testEnvironment: 'jsdom',
  transform: {
    // استخدام babel-jest لتحويل الاختبارات مع الإعداد المسبق next/babel
    // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
    '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
  },
  transformIgnorePatterns: [
    '/node_modules/',
    '^.+\\.module\\.(css|sass|scss)$',
  ],
}

يمكنك معرفة المزيد حول كل خيار تكوين في وثائق جيست.

التعامل مع صفحات الأنماط واستيراد الصور

لا تُستخدم صفحات الأنماط والصور في الاختبارات، ولكن استيرادها قد يتسبب في أخطاء، لذا سيحتاج إلى محاكاتها. أنشئ ملفات المحاكاة المشار إليها في التكوين أعلاه - fileMock.js و styleMock.js - داخل مجلد __mocks__:

__mocks__/fileMock.js
module.exports = {
  src: '/img.jpg',
  height: 24,
  width: 24,
  blurDataURL: 'data:image/png;base64,imagedata',
}
__mocks__/styleMock.js
module.exports = {}

لمزيد من المعلومات حول التعامل مع الأصول الثابتة، يُرجى الرجوع إلى وثائق جيست.

اختياري: توسيع جيست مع مطابقات مخصصة

يتضمن @testing-library/jest-dom مجموعة من المطابقات المخصصة الملائمة مثل .toBeInTheDocument() مما يجعل كتابة الاختبارات أسهل. يمكنك استيراد المطابقات المخصصة لكل اختبار عن طريق إضافة الخيار التالي إلى ملف تكوين جيست:

jest.config.js
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']

ثم، داخل jest.setup.js، أضف الاستيراد التالي:

jest.setup.js
import '@testing-library/jest-dom'

تمت إزالة extend-expect في الإصدار v6.0، لذا إذا كنت تستخدم @testing-library/jest-dom قبل الإصدار 6، فستحتاج إلى استيراد @testing-library/jest-dom/extend-expect بدلاً من ذلك.

إذا كنت بحاجة إلى إضافة المزيد من خيارات الإعداد قبل كل اختبار، فمن الشائع إضافتها إلى ملف jest.setup.js أعلاه.

اختياري: الاستيراد المطلق وأسماء مسارات الوحدات

إذا كان مشروعك يستخدم أسماء مسارات الوحدات، فستحتاج إلى تكوين جيست لحل الاستيراد عن طريق مطابقة خيار المسارات في ملف jsconfig.json مع خيار moduleNameMapper في ملف jest.config.js. على سبيل المثال:

tsconfig.json or jsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "baseUrl": "./",
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}
jest.config.js
moduleNameMapper: {
  '^@/components/(.*)$': '<rootDir>/components/$1',
}

إنشاء اختباراتك:

إضافة سكريبت اختبار إلى package.json

أضف تنفيذ جيست في وضع المراقبة إلى سكريبتات package.json:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test": "jest --watch"
  }
}

سيقوم jest --watch بإعادة تشغيل الاختبارات عند تغيير ملف. لمزيد من خيارات واجهة سطر أوامر جيست، يُرجى الرجوع إلى وثائق جيست.

إنشاء أول اختباراتك

مشروعك جاهز الآن لتشغيل الاختبارات. اتبع اصطلاح جيست بإضافة الاختبارات إلى مجلد __tests__ في الدليل الجذري لمشروعك.

على سبيل المثال، يمكننا إضافة اختبار للتحقق مما إذا كان مكون <Home /> يعرض عنوانًا بنجاح:

__tests__/index.test.js
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
import '@testing-library/jest-dom'

describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />)

    const heading = screen.getByRole('heading', {
      name: /welcome to next\.js!/i,
    })

    expect(heading).toBeInTheDocument()
  })
})

اختياريًا، أضف اختبار لقطة لتتبع أي تغييرات غير متوقعة في مكون <Home />:

__tests__/snapshot.js
import { render } from '@testing-library/react'
import Home from '../pages/index'

it('renders homepage unchanged', () => {
  const { container } = render(<Home />)
  expect(container).toMatchSnapshot()
})

معلومة جيدة: لا يجب تضمين ملفات الاختبار داخل موجه الصفحات لأن أي ملف داخل موجه الصفحات يعتبر مسارًا.

تشغيل مجموعة الاختبارات الخاصة بك

شغّل npm run test لتشغيل مجموعة الاختبارات الخاصة بك. بعد نجاح أو فشل اختباراتك، ستلاحظ قائمة بأوامر جيست التفاعلية التي ستكون مفيدة عند إضافة المزيد من الاختبارات.

لمزيد من القراءة، قد تجد هذه المصادر مفيدة:

حزم وأمثلة المجتمع

قام مجتمع Next.js بإنشاء حزم ومقالات قد تجدها مفيدة:

لمزيد من المعلومات حول ما يمكن قراءته بعد ذلك، نوصي بـ:

  • pages/basic-features/environment-variables#test-environment-variables