اختبار تطبيقات Next.js

من موسوعة حسوب

نلقي نظرة في هذه الصفحة على طرق اختبار تطبيقات Next.js باستخدادم أدوات مثل Cypress و Playwright و Jest مع مكتبة اختبار React.

اختبار تطبيقات Next.js باستخدام Cypress

تُستخدم المكتبة Cypress في تنفيذ اختبارات مشتركة للواجهتين الأمامية والخلفية End-to-End واختبارات التكامل Integration Testing.

بداية سريعة

بإمكانك استخدام create-next-app مع المثال with-cypress لتبدأ العمل بسرعة:

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

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

ثبّت حزمة cypress كالتالي:

npm install --save-dev cypress

أضف Cypress إلى حقل السكربتات "scripts" في ملف package.json:

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

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

npm run cypress

يمكنك الاطلاع على الأمثلة الموّلدة وتوثيق Cypress لتألف التعامل مع هذ المكتبة.

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

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

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

export default function Home() {
  return (
    <nav>
      <Link href="/about">
        <a>About</a>
      </Link>
    </nav>
  )
}
// pages/about.js
export default function About() {
  return (
    <div>
      <h1>About Page</h1>
    </div>
  )
}

أضف اختبارًا للتحقق من إمكانية التنقل بينهما:

// cypress/integration/app.spec.js

describe('Navigation', () => {
  it('should navigate to the about page', () => {
    // index ابدأ بالصفحة
    cy.visit('http://localhost:3000/')

    //ثم انقر عليها "about" التي تضم href جد رابطًا له السمة  
    cy.get('a[href*="about"]').click()

    //"/about" ينبغي أن يضم العنوان الجدبد  
    cy.url().should('include', '/about')

    //"About page" مع  h1 ينبغي أن تضم الصفحة الجديدة العنصر 
    cy.get('h1').contains('About Page')
  })
})

بإمكانك استخدام ("/")cy.visit بدلًا من cy.visit("http://localhost:3000/")، إن أضفت "baseUrl": "http://localhost:3000" إلى ملف التهيئة cypress.json.

تنفيذ اختبارات Cypress

تختبر Cypress تطبيقات Next.js حقيقية لذلك لا بد أن يعمل خادم Next.js قبل أن تبدأ Cypress. ننصحك بتطبيق اختباراتك على شيفرة الإنتاج لتقف بشكل أوضح على سلوك تطبيقك.

نفّذ الأمر npm run build ثم npm run start وبعد ذلك الأمر npm run cypress في نافذة جديدة للطرفية لتشغيل Cypress.

ملاحظة: بإمكانك كحل بديل أن تُثبِّت الحزمة start-server-and-test وتضيفها إلى حقل السكربتات "scripts" في ملف package.json كالتالي:

"test": "start-server-and-test start http://localhost:3000 cypress"

وذلك كي تُشغّل خادم إنتاج Next.js إلى جوار Cypress، وتذكرأن تعيد بناء التطبيق بعد إجراء تعديلات جديدة.

التحضير لعملية التكامل المتواصل CI في تطبيقات Next.js

ربما ستلاحظ أن Cypress تشغل متصفحًا تفاعليًا ليس نموذجيًا لبيئة التكامل المتواصل Continuous Integration. بإمكانك أن تُشغّل Cypress أيضًا دون ترويسة بتنفيذ الأمر cypress run:

// package.json

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

يمكنك الاطلاع على المزيد من المعلومات حول Cypress والتتكامل المتواصل من خلال المصادر التالية:

اختبار تطبيقات Next.js باستخدام Playwright

Playwright هو إطار عمل للاختبارات يتيح لك أتمتة Chromium و Firefox و WebKit من خلال واجهة برمجية واحدة. يُمكنك استخدام Playwright في تنفيذ الاختبارات المشتركة للواجهتين واختبارات التكامل عبر جميع المنصات.

بداية سريعة

بإمكانك استخدام create-next-app مع المثال with-playwright لتبدأ العمل بسرعة. سيُنشئ ذلك مشروعًا جديدًا أعدت فيه Next.js بشكل كامل.

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

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

بإمكانك أن تستخدم npm init playwright أيضًا لإضافة Playwright إلى مشروع NPM موجود مسبقًا. ثبِّت الحزمة playwright/test@ لكي تبدأ يدويًا العمل مع Playwright.

npm install --save-dev @playwright/test

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

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

إنشاء أول اختبار مشترك للواجهتين باستخدام Playwright

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

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

export default function Home() {
  return (
    <nav>
      <Link href="/about">
        <a>About</a>
      </Link>
    </nav>
  )
}
// pages/about.js
export default function About() {
  return (
    <div>
      <h1>About Page</h1>
    </div>
  )
}

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

// e2e/example.spec.ts

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

test('should navigate to the about page', async ({ page }) => {
  // يُحدد العنوان الجذري من قبل خادم ويب وفقًا للملف) index ابدأ من الصفحة 
  // the playwright.config.ts)
  await page.goto('http://localhost:3000/')
  //وانقر عليه 'About Page' جد عنصرًا يحمل النص 
  await page.click('text=About Page')
  //(باستخدام العنوان الجذري) "/about" الجديد هو  URL ينبغي أن يكون عنوان 
  await expect(page).toHaveURL('http://localhost:3000/about')
  //"About page" مع  h1 ينبغي أن تضم الصفحة الجديدة العنصر  
  await expect(page.locator('h1')).toContainText('About Page')
})

بإمكانك استخدام ("/")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 في وضع التكامل المتواصل

تُنفَّذ Playwright اختباراتك دون ترويسات افتراضيًا. ولتثبيت كل اعتماديات Playwright نفّذ الأمر npx playwright install-deps

بإمكانك الاطلاع أكثر على Playwright والتكامل المتواصل من خلال المصادر التالية:

استخدام Jest ومكتبة اختبار React لاختبار تطبيقات Next.js

تُستسخدم هاتين المكتبتين مرارًا في إجراء اختبار الوحدات Unit Testing. وهنالك ثلاث طرق لكي تستخدم Jest في تطبيق Next.js:

  1. استخدام أحد أمثلة الإقلاع السريع.
  2. مع مُصرَّف Rust الخاص بتطبيقات.
  3. باستخدام Babel.

بداية سريعة

بإمكانك استخدام create-next-app مع المثال with-jest لتبدأ العمل بسرعة:

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

إعداد المكتبة Jest (مع مُصرّف Rust)

تتمتع Next.js ابتداءً من النسخة 12 بدعم مدمج للمكتبة Jest. ولإعداد Jest، ثبِّت الاعتماديات التالية jest و @testing-library/react و @testing-library/jest-dom:

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

أنشئ الملف jest.config.js في المجلد الجذري لمشروعك وأضف الشيفرة التالية:

// jest.config.js
const nextJest = require('next/jest')

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

// Jest أضف أية إعدادات أخرى تنوي تمريرها إلى 
const customJestConfig = {
  // أضف أية خيارات إعداد أخرى قبل تنفيذ كل اختبار
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // مع عناوين جذرية ستحتاج إلى مايلي لإخفاء العمل TypeScript إن كنت تستخدم 
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: 'jest-environment-jsdom',
}
//قادر على  next/jest بهذا الشكل للتأكد من أن createJestConfig تُصدّر 
//بشكل غير متزامن Next.js تحميل إعدادات 
module.exports = createJestConfig(customJestConfig)

تهيئ next/jest المكتبة Jest تحت الستار نيابة عنك بما في ذلك:

  • إعداد transform باستخدام SWC.
  • التقليد التلقائي للتنسيقات الموَّرثة (css. و module.css. وما يماثلها من تنسيقات SCSS).
  • تحميل env. (وكل ما يماثلها) ضمن process.env.
  • تجاهل node_modules عند تحليل وتنفيذ الاختبارات والتحويلات transforms.
  • تجاهل next. عند تحليل الاختبار.
  • تحميل next.config.js من أجل الرايات التي تفعّل تحويلات SWC.

ضبط إعدادات Jest مع Babel

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

إليك الخيارات المستحسنة لتهيئة Jest من أجل تطبيقات Next.js:

// jest.config.js
module.exports = {
  collectCoverageFrom: [
    '**/*.{js,jsx,ts,tsx}',
    '!**/*.d.ts',
    '!**/node_modules/**',
  ],
  moduleNameMapper: {
    // (CSS مع وحدات) المُدرج CSS التعامل مع تنسيقات 
    // https://jestjs.io/docs/webpack#mocking-css-modules
    '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

    //(CSS دون وحدات) المُدرج CSS التعامل مع تنسيقات 
    // Handle CSS imports (without CSS modules)
    '^.+\\.(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: {
    //next/babel لنقل شيفرة الاختبارات باستخدام  babel-jest استخدام
    // 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)$',
  ],
}

بإمكانك الاطلاع على كل خيار من خيارات الإعداد في توثيق Jest.

إدراج الصور وصفحات التسيق الموّرثة CSS

لا تُستخدم الصور والتنسيقات الموَّرثة في الاختبارات لكن إدراجها قد يقود إلى أخطاء، وبالتالي لا بد من تقليدها. انشئ ملفات التقليد mock files المُشار إليها في الإعدادات السابقة (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 = {}

لمعلومات أكثر عن التعامل مع الموجودات الساكنة راجع توثيق Jest.

توسعة Jest بمطابقات مخصصة (اختياري)

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

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

ثم أدرج ما يلي داخل الملف jest.setup.js:

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

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

الإدراجات ذات المسارات المطلقة والبديلة

إن استخدمت بدائل مسار الوحدة البرمجية Module Path Aliases، فلا بد من تهيئة Jest ليحلل هذه الإدراجات بمطابقة الخيار "paths" (المسارات) في الملف jsconfig.json مع الخيار moduleNameMapper في الملف jest.config.js. إليك مثالًا:

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

إنشاء الاختبارات الخاصة بك

  • إضافة سكربت الاختبار إلى الملف package.json: أضف خيار تنفيذ اختبارات Jest إلى سكربتات package.json ، إذ يعيد الخيار تشغيل الاختبار عندما يتغير الملف:
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "test": "jest --watch"
}
  • إنشاء أول اختباراتك: أصبح مشروعك الآن جاهزًا لتنفيذ الاختبارات. اتبع تقاليد Jest بإضافة الاختبارات إلى المجلد __tests__ في المجلد الجذري لمشروعك. يمكنك مثلًا إضافة اختبار يتحقق من تصير المكوّن </ Home> لعنوان بشكل صحيح:
// __tests__/index.test.jsx

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()
  })
})

وكخيار آخر، أضف اختبار اللقطات snapshot test لتعقب التغيرات غير المتوقعة في المكوّن </ 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()
})

ملاحظة: لا ينبغي وضع ملفات الاختبار مباشرة ضمن مجلد الصفحات لأن كل ملف في هذا المجلد يُعدّث وجهة route.

  • تشغيل مجموعة الاختبارات: نفّذ ببساطة الأمر npm run test وستلاحظ مجموعة من الأوامر التفاعلية يولّدها Jest سواء أخفق أو نجح الاختبار، ولهذا فائدته عند إضافة اختبارات أكثر.

لمعلومات أكثر، يمكنك الاطلاع على المصادر التالية:

حزم طوّرها مجتمع Next.js وأمثلة متنوعة

طوّر مجتمع Next.js حزمًا قد تجدها مفيدة منها:

المصادر

  • الصفحة Testing من توثيق Next.js الرسمي.