كيفية اختبار تطبيق React باستخدام Jest ومكتبة اختبار React

اختار المؤلف Vets Who Code لتلقي تبرع كجزء من برنامج Write for DOnations.

مقدمة

الحصول على تغطية اختبار قوية ضروري لبناء الثقة في تطبيق الويب الخاص بك. Jest هو برنامج تشغيل اختبارات JavaScript يوفر موارد لكتابة الاختبارات وتشغيلها. مكتبة اختبارات رياكت توفر مجموعة من مساعدي الاختبارات التي تهيئ اختباراتك بناءً على تفاعلات المستخدم بدلاً من تفاصيل تنفيذ المكونات. كل من Jest و React Testing Library مدمجين مسبقًا مع Create React App ويتماشون مع المبدأ التوجيهي الذي يفيد بأن اختبار التطبيقات يجب أن يشبه كيف سيتم استخدام البرنامج.

في هذا البرنامج التعليمي، ستختبر الشفرة الحية والتفاعلات في مشروع عيني يحتوي على عناصر واجهة مستخدم متنوعة. ستستخدم Jest لكتابة وتشغيل الاختبارات الوحدوية، وستنفذ مكتبة اختبارات رياكت كمكتبة مساعدة لنموذج DOM (Document Object Model) للتعامل مع التفاعل مع المكونات.

المتطلبات المسبقة

لإكمال هذا البرنامج التعليمي، ستحتاج إلى:

  • Node.js الإصدار 14 أو أحدث مثبت على جهازك المحلي. لتثبيت Node.js على macOS أو Ubuntu 18.04، اتبع الخطوات الموجودة في كيفية تثبيت Node.js وإنشاء بيئة تطوير محلية على macOS أو القسم التثبيت باستخدام PPA من كيفية تثبيت Node.js على Ubuntu 18.04.

  • يجب أن يكون لديك إصدار npm 5.2 أو أعلى على جهازك المحلي، الذي ستحتاج إليه لاستخدام Create React App و npx في المشروع العيني. إذا لم تقم بتثبيت npm بجانب Node.js، فافعل ذلك الآن. بالنسبة لنظام Linux، استخدم الأمر sudo apt install npm.

    • لتعمل حزم npm في هذا البرنامج التعليمي، قم بتثبيت حزمة build-essential. بالنسبة لنظام Linux، استخدم الأمر sudo apt install build-essential.
  • Git يجب أن يكون مثبتًا على جهازك المحلي. يمكنك التحقق مما إذا كان Git مثبتًا على جهاز الكمبيوتر الخاص بك أو القيام بعملية التثبيت لنظام التشغيل الخاص بك باستخدام كيفية تثبيت Git على Ubuntu 20.04.

  • المعرفة بـ React، التي يمكنك تطويرها باستخدام سلسلة كيفية البرمجة في React.js. نظرًا لأن المشروع المثالي قد تم تهيئته مسبقًا باستخدام Create React App، فليس عليك تثبيتها بشكل منفصل.

  • المعرفة بـ Jest كمشغل اختبارات أو إطار عمل مفيدة ولكن غير مطلوبة. نظرًا لأن Jest مُعبأة مسبقًا مع Create React App، فليس عليك تثبيتها بشكل منفصل.

الخطوة 1 — إعداد المشروع

في هذه الخطوة، ستقوم بنسخ مشروع عينة وتشغيل مجموعة الاختبارات. يستخدم المشروع العيني ثلاثة أدوات رئيسية: Create React App و Jest و React Testing Library. يُستخدم Create React App لبدء تطبيق React ذو صفحة واحدة. يُستخدم Jest كمشغل اختبارات، وتوفر React Testing Library مساعدات اختبار لتنظيم الاختبارات حول تفاعلات المستخدم.

للبدء، ستقوم بنسخ تطبيق React مُعد مسبقًا من GitHub. ستعمل مع تطبيق دليل الكلاب، وهو مشروع عيني يستفيد من واجهة برمجة التطبيقات للكلاب لبناء نظام بحث وعرض لمجموعة من صور الكلاب بناءً على فصيلة محددة.

لنسخ المشروع من Github، افتح الطرفية الخاصة بك وقم بتشغيل الأمر التالي:

  1. git clone https://github.com/do-community/doggy-directory

سترى إخراجًا مشابهًا لهذا:

Output
Cloning into 'doggy-directory'... remote: Enumerating objects: 64, done. remote: Counting objects: 100% (64/64), done. remote: Compressing objects: 100% (48/48), done. remote: Total 64 (delta 21), reused 55 (delta 15), pack-reused 0 Unpacking objects: 100% (64/64), 228.16 KiB | 3.51 MiB/s, done.

قم بالتغيير إلى مجلد doggy-directory:

  1. cd doggy-directory

قم بتثبيت تبعيات المشروع:

  1. npm install

سيقوم أمر npm install بتثبيت جميع تبعيات المشروع المُعرفة في ملف package.json.

بعد تثبيت التبعيات، يمكنك إما عرض النسخة المنشورة للتطبيق أو يمكنك تشغيل التطبيق محليًا باستخدام الأمر التالي:

  1. npm start

إذا اخترت تشغيل التطبيق محليًا، سيتم فتحه على http://localhost:3000/. سترى الإخراج التالي في الطرفية:

Output
Compiled successfully! You can now view doggy-directory in the browser. Local: http://localhost:3000 On Your Network: http://network_address:3000

بعد التشغيل، ستبدو الصفحة الرئيسية للتطبيق بهذا الشكل:

تم تثبيت تبعيات المشروع، والتطبيق يعمل الآن. فيما بعد، افتح محطة أخرى وقم بتشغيل الاختبارات بالأمر التالي:

  1. npm test

الأمر npm test يبدأ الاختبارات في وضع المراقبة التفاعلية مع Jest كمشغل اختبارات له. عندما يكون في وضع المراقبة، تعيد الاختبارات تشغيلها تلقائيًا بعد تغيير ملف. ستقوم الاختبارات بالتشغيل كلما قمت بتغيير ملف وستخبرك ما إذا كان هذا التغيير قد نجح في الاختبارات.

بعد تشغيل npm test للمرة الأولى، سترى هذا الإخراج في المحطة:

Output
No tests found related to files changed since last commit. Press `a` to run all tests, or run Jest with `--watchAll`. Watch Usage › Press a to run all tests. › Press f to run only failed tests. › Press q to quit watch mode. › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press Enter to trigger a test run.

الآن بعد أن لديك تطبيق المثال ومجموعة الاختبارات الجارية، يمكنك بدء الاختبار على صفحة الهبوط.

الخطوة 2 — اختبار صفحة الهبوط

بشكل افتراضي، سيبحث Jest عن الملفات التي تحمل امتداد .test.js والملفات التي تحمل امتداد .js في مجلدات __tests__. عندما تقوم بإجراء تغييرات على ملفات الاختبار ذات الصلة، سيتم اكتشافها تلقائيًا. مع تعديل حالات الاختبار، سيتم تحديث الإخراج تلقائيًا. الملف التجريبي الذي تم إعداده لمشروع العينة doggy-directory مهيأ مع كود أدنى قبل إضافة نماذج الاختبار. في هذه الخطوة، ستقوم بكتابة اختبارات للتحقق من أن صفحة الهبوط في التطبيق ستحمل قبل القيام بعملية بحث.

افتح src/App.test.js في محرر النصوص الخاص بك لرؤية الكود التالي:

src/App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders the landing page', () => {
  render(<App />);
});

A minimum of one test block is required in each test file. Each test block accepts two required parameters: the first argument is a string representing the name of the test case; the second argument is a function that holds the expectations of the test.

داخل الدالة، هناك طريقة render التي يوفرها مكتبة اختبار رياكت لتقديم مكونك إلى DOM. مع المكون الذي تريد اختباره مقدماً إلى DOM بيئة الاختبار، يمكنك الآن البدء في كتابة الكود للتأكد من الوظيفة المتوقعة.

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

src/App.test.js
...
test('renders the landing page', () => {
  render(<App />);
  
  expect(screen.getByRole("heading")).toHaveTextContent(/Doggy Directory/);
  expect(screen.getByRole("combobox")).toHaveDisplayValue("Select a breed");
  expect(screen.getByRole("button", { name: "Search" })).toBeDisabled();
  expect(screen.getByRole("img")).toBeInTheDocument();
});

تُستخدم دالة expect في كل مرة تريد فيها التحقق من نتيجة معينة، وتقبل وسيطة واحدة تمثل القيمة التي ينتجها كودك. معظم دوال expect مقترنة بدالة matcher للتأكد من شيء معين بشأن قيمة معينة. لمعظم هذه التأكيدات، ستستخدم دوال مقارنة إضافية يوفرها jest-dom لتسهيل التحقق من الجوانب الشائعة الموجودة في DOM. على سبيل المثال، .toHaveTextContent هو مقارن الدالة لدالة expect في السطر الأول، بينما getByRole("heading") هو المحدد لالتقاط عنصر DOM.

توفر مكتبة اختبار رياكت كائن screen كطريقة مريحة للوصول إلى الاستعلامات اللازمة للتأكد من صحة بيئة DOM الاختبارية. بشكل افتراضي، توفر مكتبة اختبار رياكت استعلامات تسمح لك بتحديد العناصر داخل DOM. هناك ثلاث فئات رئيسية من الاستعلامات:

  • getBy* (الأكثر استخدامًا)
  • queryBy* (المستخدم عند اختبار غياب عنصر دون رمي خطأ)
  • findBy* (المستخدم عند اختبار الشفرة الغير متزامنة)

يُعتبر كل نوع من الاستعلامات له غرض محدد سيُوضح لاحقًا في البرنامج التعليمي. في هذه الخطوة، ستركز على استعلام getBy*، وهو نوع الاستعلام الأكثر شيوعًا. لرؤية قائمة شاملة لمختلف تفاصيل الاستعلام، يُمكنك مراجعة “ورقة غش React” cheatsheet للبحث.

أدناه صورة موضحة لصفحة هبوط دليل الكلاب تشير إلى كل قسم يغطيه الاختبار الأول (على تقديم صفحة الهبوط):

توقع كل دالة expect ضد التالي (الموضح في الصورة الموضحة أعلاه):

  1. تتوقع أن يحتوي العنصر بدور heading على تطابق جزئي لـ دليل الكلاب.
  2. تتوقع أن يحتوي إدخال الاختيار على قيمة عرض دقيقة اختر فصيلة.
  3. تتوقع أن يكون زر البحث معطلًا لأن لم يتم اختيار أي شيء.
  4. تتوقع وجود صورة العنصر المكاني في المستند لأنه لم يتم إجراء بحث.

عند الانتهاء، احفظ ملف src/App.test.js. نظرًا لتشغيل الاختبارات في وضع المراقبة، ستُسجّل التغييرات تلقائيًا. إذا لم تُسجّل التغييرات تلقائيًا، قد تحتاج إلى إيقاف وإعادة تشغيل مجموعة الاختبارات.

الآن، عندما تنظر إلى اختباراتك في الطرفية، سترى الناتج التالي:

Output
PASS src/App.test.js ✓ renders the landing page (172 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.595 s, estimated 5 s Ran all test suites related to changed files. Watch Usage: Press w to show more.

في هذه الخطوة، كتبت اختبارًا أوليًا للتحقق من العرض الأولي لصفحة الهبوط لدليل Doggy. في الخطوة التالية، ستتعلم كيفية تزييف استدعاء API لاختبار الشفرة الغير متزامنة.

الخطوة 3 — تزييف طريقة fetch

في هذه الخطوة، ستستعرض أحد الطرق لتزييف طريقة fetch في جافاسكريبت. بينما هناك طرق عديدة لتحقيق ذلك، سيستخدم هذا التنفيذ أساليب spyOn و mockImplementation في Jest.

عند الاعتماد على واجهات برمجة التطبيقات الخارجية، هناك فرصة لأن تتوقف واجهة برمجة التطبيقات أو تأخذ وقتًا طويلاً لإرجاع استجابة. يوفر تزييف طريقة fetch بيئة متسقة ومتوقعة، مما يمنحك المزيد من الثقة في اختباراتك. يُعتبر آلية تزييف واجهة برمجة التطبيقات ضرورية لتشغيل الاختبارات بشكل صحيح عند استخدام واجهة برمجة تطبيقات خارجية.

ملاحظة: في محاولة لتبسيط هذا المشروع، ستقوم بتقليد طريقة الاسترجاع. ومع ذلك، يُنصح باستخدام حل أكثر قوة مثل Mock Service Worker (MSW) عند تقليد الكود اللازم للأكواد الغير متزامنة في أكواد أكبر جاهزة للإنتاج.

افتح src/mocks/mockFetch.js في محرر النصوص لمراجعة كيفية عمل طريقة mockFetch:

src/mocks/mockFetch.js
const breedsListResponse = {
    message: {
        boxer: [],
        cattledog: [],
        dalmatian: [],
        husky: [],
    },
};

const dogImagesResponse = {
    message: [
        "https://images.dog.ceo/breeds/cattledog-australian/IMG_1042.jpg ",
        "https://images.dog.ceo/breeds/cattledog-australian/IMG_5177.jpg",
    ],
};

export default async function mockFetch(url) {
    switch (url) {
        case "https://dog.ceo/api/breeds/list/all": {
            return {
                ok: true,
                status: 200,
                json: async () => breedsListResponse,
            };
        }
        case "https://dog.ceo/api/breed/husky/images" :
        case "https://dog.ceo/api/breed/cattledog/images": {
            return {
                ok: true,
                status: 200,
                json: async () => dogImagesResponse,
            };
        }
        default: {
            throw new Error(`Unhandled request: ${url}`);
        }
    }
}

تُعيد طريقة mockFetch كائنًا يشبه بشكل كبير هيكل ما يعود به استدعاء fetch في الرد على استدعاءات API داخل التطبيق. تعتبر طريقة mockFetch ضرورية لاختبار الوظائف غير المتزامنة عبر منطقتين في تطبيق Doggy Directory: القائمة المنسدلة التي تملأ قائمة السلالات واستدعاء API لاسترجاع صور الكلاب عندما يتم البحث.

أغلق src/mocks/mockFetch.js. الآن بعد أن فهمت كيفية استخدام طريقة mockFetch في اختباراتك، يمكنك استيرادها إلى ملف الاختبار الخاص بك. سيتم تمرير الدالة mockFetch كوسيط لطريقة mockImplementation وسيتم استخدامها بمثابة تنفيذ مزيف لواجهة fetch.

في src/App.test.js، أضف الأسطر المظللة من الكود لاستيراد طريقة mockFetch:

src/App.test.js
import { render, screen } from '@testing-library/react';
import mockFetch from "./mocks/mockFetch";
import App from './App';

beforeEach(() => {
   jest.spyOn(window, "fetch").mockImplementation(mockFetch);
})

afterEach(() => {
   jest.restoreAllMocks()
});
...

هذا الكود سيعد ويفك تنفيذ التقليد حتى تبدأ كل اختبار من منصة متساوية.

jest.spyOn(window, "fetch"); ينشئ دالة تقليدية ستتتبع استدعاءات طريقة fetch المرتبطة بالمتغير العالمي window في DOM.

.mockImplementation(mockFetch); يقبل دالة سيتم استخدامها لتنفيذ الأسلوب المزيف. نظرًا لأن هذا الأمر يعتمد تنفيذ fetch الأصلي، فإنه سيتم تشغيله كلما تم استدعاء fetch داخل رمز التطبيق.

عند الانتهاء، احفظ ملف src/App.test.js.

الآن، عندما تنظر إلى اختباراتك في الطرفية، ستتلقى الإخراج التالي:

Output
console.error Warning: An update to App inside a test was not wrapped in act(...). When testing, code that causes React state updates should be wrapped into act(...): act(() => { /* fire events that update state */ }); /* assert on the output */ This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act at App (/home/sammy/doggy-directory/src/App.js:5:31) 18 | }) 19 | .then((json) => { > 20 | setBreeds(Object.keys(json.message)); | ^ 21 | }); 22 | }, []); 23 | ... PASS src/App.test.js ✓ renders the landing page (429 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.178 s, estimated 2 s Ran all test suites related to changed files.

يُخطرك التحذير بأن حدث تحديث للحالة عندما لم يكن متوقعًا. ومع ذلك، يشير الإخراج أيضًا إلى أن الاختبارات قامت بتحاكي بنجاح الطريقة fetch.

في هذه الخطوة، قمت بتقليد الطريقة fetch ودمجت هذه الطريقة في مجموعة اختبار. على الرغم من أن الاختبار يمر، إلا أنه لا يزال عليك معالجة التحذير.

الخطوة 4 — إصلاح تحذير act

في هذه الخطوة، ستتعلم كيفية إصلاح تحذير act الذي ظهر بعد التغييرات في الخطوة 3.

يحدث تحذير act لأنك قمت بتقليد الطريقة fetch، وعندما يتم تثبيت المكون، فإنه يقوم بعملية استدعاء API لالتقاط قائمة السلالات. تُخزن قائمة السلالات في متغير حالة يملأ عنصر الـ option داخل مدخل الاختيار.

الصورة أدناه توضح كيفية مظهر مدخل الاختيار بعد إجراء استدعاء API ناجح لملء قائمة السلالات:

يتم إطلاق التحذير لأن الحالة مُضبَّطة بعد انتهاء تقديم كتلة الاختبار المكوّن.

لإصلاح هذه المشكلة، أضف التعديلات المُظلَّلة التالية إلى حالة الاختبار في src/App.test.js:

src/App.test.js
...
test('renders the landing page', async () => {
   render(<App />);
   
   expect(screen.getByRole("heading")).toHaveTextContent(/Doggy Directory/);
   expect(screen.getByRole("combobox")).toHaveDisplayValue("Select a breed");
   expect(await screen.findByRole("option", { name: "husky"})).toBeInTheDocument();
   expect(screen.getByRole("button", { name: "Search" })).toBeDisabled();
   expect(screen.getByRole("img")).toBeInTheDocument();
});

تُخبر كلمة المفتاح async Jest بأن الكود الغير متزامن يعمل نتيجة للمكالمة إلى واجهة برمجة التطبيقات التي تحدث عند تحميل المكوّن.

A new assertion with the findBy query verifies that the document contains an option with the value of husky. findBy queries are used when you need to test asynchronous code that is dependent on something being in the DOM after a period of time. Because the findBy query returns a promise that gets resolved when the requested element is found in the DOM, the await keyword is used within the expect method.

عند الانتهاء، احفظ التغييرات التي تم إجراؤها في src/App.test.js.

مع الإضافات الجديدة، ستلاحظ الآن أن تحذير act لم يعد موجودًا في اختباراتك:

Output
PASS src/App.test.js ✓ renders the landing page (123 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.942 s, estimated 2 s Ran all test suites related to changed files. Watch Usage: Press w to show more.

في هذه الخطوة، تعلمت كيفية إصلاح التحذير act الذي يمكن أن يحدث عند العمل مع الكود غير المتزامن. في الخطوة التالية، ستضيف حالة اختبار ثانية للتحقق من الوظائف التفاعلية لتطبيق دليل الكلاب.

الخطوة 5 — اختبار ميزة البحث

في الخطوة النهائية، ستكتب حالة اختبار جديدة للتحقق من ميزة البحث وعرض الصور. ستستفيد من مجموعة متنوعة من الاستعلامات وطرق واجهة برمجة التطبيقات لتحقيق التغطية الاختبارية السليمة.

ارجع إلى الملف src/App.test.js في محرر النصوص الخاص بك. في أعلى الملف، قم بإستيراد مكتبة المساعد user-event والطريقة اللازمة للإنتظار waitForElementToBeRemoved بشكل غير متزامن إلى ملف الاختبار بإستخدام الأوامر المميزة التالية:

src/App.test.js
import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';import userEvent from '@testing-library/user-event'; 
...

ستقوم باستخدام هذه الاستيرادات لاحقًا في هذا القسم.

بعد طريقة الاختبار test() الأولية، قم بإضافة كتلة جديدة للاختبار الغير متزامن وقم بتقديم عنصر App باستخدام الكود التالي:

src/App.test.js
...
test("should be able to search and display dog image results", async () => {
   render(<App />);
})

بعد تقديم العنصر، يمكنك الآن إضافة وظائف تتحقق من الميزات التفاعلية لتطبيق دليل الكلاب.

لا تزال في src/App.test.js، قم بإضافة كتل الكود المميزة ضمن الطريقة test() الثانية:

src/App.test.js
...
test("should be able to search and display dog image results", async () => {
   render(<App />);
   
   //تحاكي اختيار خيار والتحقق من قيمته
   const select = screen.getByRole("combobox");
   expect(await screen.findByRole("option", { name: "cattledog"})).toBeInTheDocument();
   userEvent.selectOptions(select, "cattledog");
   expect(select).toHaveValue("cattledog");
})

الجزء المميز أعلاه سيحاكي اختيار فصيلة كلب والتحقق من أن القيمة الصحيحة معروضة.

استعلم العنصر المحدد باستخدام الدالة getByRole وقم بتعيينه إلى المتغير select.

بنفس الطريقة التي قمت بها بتصحيح تحذير act في الخطوة 4، استخدم الاستعلام findByRole للانتظار حتى يظهر الخيار cattledog في المستند قبل المتابعة مع التحقق من المزيد من الادعاءات.

الكائن userEvent المستورد في وقت سابق سيحاكي تفاعلات المستخدم الشائعة. في هذا المثال، تختار طريقة selectOptions الخيار cattledog الذي انتظرته في السطر السابق.

السطر الأخير يؤكد أن المتغير select يحتوي على قيمة cattledog المحددة أعلاه.

القسم التالي الذي ستقوم بإضافته إلى كتلة الاختبارات في جافاسكريبت test() سيبدأ طلب البحث للعثور على صور الكلاب استنادًا إلى السلالة المحددة وتأكيد وجود حالة التحميل.

أضف السطور المظللة:

src/App.test.js
...
test("should be able to search and display dog image results", async () => {
   render(<App />);
    
   //... تحاكي اختيار الخيار والتحقق من قيمته

  // تحاكي بدء طلب البحث
   const searchBtn = screen.getByRole("button", { name: "Search" });
   expect(searchBtn).not.toBeDisabled();
   userEvent.click(searchBtn);

   // يظهر حالة التحميل ويتمتع بإزالتها بمجرد عرض النتائج
   await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i));
})

استعلم الكائن getByRole يحدد زر البحث ويسند ذلك إلى المتغير searchBtn.

يتحقق العملي toBeDisabled jest-dom من أن زر البحث غير معطل عندما يتم اختيار سلالة.

تحاكي الطريقة click على كائن userEvent النقر على زر البحث.

الدالة waitForElementToBeRemoved المساعدة الكاملة المستوردة مسبقًا ستنتظر ظهور واختفاء رسالة Loading أثناء قيام مكالمة API البحث. queryByText داخل استدعاء waitForElementToBeRemoved تتحقق من غياب عنصر دون إلقاء خطأ.

الصورة أدناه تُظهر الحالة التحميل التي ستُعرَض عندما يكون البحث قيد التقدم:

بعد ذلك، أضف الكود الجافا سكريبت التالي للتحقق من عرض الصورة وعدد النتائج:

src/App.test.js
...
test("should be able to search and display dog image results", async () => {
   render(<App />)
   
   //...محاكاة اختيار خيار والتحقق من قيمته
   //...محاكاة طلب البحث
   //...يُعرض حالة التحميل ويتمتعل إزالتها بمجرد عرض النتائج
          
   //التحقق من عرض الصورة وعدد النتائج
   const dogImages = screen.getAllByRole("img");
   expect(dogImages).toHaveLength(2);
   expect(screen.getByText(/2 Results/i)).toBeInTheDocument();
   expect(dogImages[0]).toHaveAccessibleName("cattledog 1 of 2");
   expect(dogImages[1]).toHaveAccessibleName("cattledog 2 of 2");
})

استعلام getAllByRole سيختار كل صور الكلاب ويعينها إلى متغير dogImages. الاستعلام بالصيغة *AllBy* يعيد مصفوفة تحتوي على عناصر متعددة تطابق الدور المحدد. الصيغة *AllBy* تختلف عن الصيغة ByRole، التي يمكن أن تعيد فقط عنصرًا واحدًا.

كانت التنفيذ المشابه لـ fetch يحتوي على عنواني URL للصور ضمن الاستجابة. باستخدام جهاز المطابقة toHaveLength في Jest، يمكنك التحقق من أن هناك صورتين معروضتين.

ستقوم الاستعلام getByText بالتحقق من ظهور العدد الصحيح من النتائج في الزاوية اليمنى.

تتحقق التأكيدات باستخدام المطابقين toHaveAccessibleName من أن النص البديل المناسب مرتبط بالصور الفردية.

A completed search displaying images of the dog based on the breed selected along with the number of results found will look like this:

عندما تجمع جميع قطع الشفرة الجديدة لجافا سكريبت، سيبدو ملف App.test.js كالتالي:

src/App.test.js
import {render, screen, waitForElementToBeRemoved} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import mockFetch from "./mocks/mockFetch";
import App from './App';

beforeEach(() => {
   jest.spyOn(window, "fetch").mockImplementation(mockFetch);
})

afterEach(() => {
   jest.restoreAllMocks();
});

test('renders the landing page', async () => {
   render(<App />);

   expect(screen.getByRole("heading")).toHaveTextContent(/Doggy Directory/);
   expect(screen.getByRole("combobox")).toHaveDisplayValue("Select a breed");
   expect(await screen.findByRole("option", { name: "husky"})).toBeInTheDocument()
   expect(screen.getByRole("button", { name: "Search" })).toBeDisabled();
   expect(screen.getByRole("img")).toBeInTheDocument();
});

test("should be able to search and display dog image results", async () => {
   render(<App />);

   //نموذج لاختيار خيار والتحقق من قيمته
   const select = screen.getByRole("combobox");
   expect(await screen.findByRole("option", { name: "cattledog"})).toBeInTheDocument();
   userEvent.selectOptions(select, "cattledog");
   expect(select).toHaveValue("cattledog");

   //بدء طلب البحث
   const searchBtn = screen.getByRole("button", { name: "Search" });
   expect(searchBtn).not.toBeDisabled();
   userEvent.click(searchBtn);

   //عرض حالة التحميل وإزالتها بمجرد عرض النتائج
   await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i));

   //التحقق من عرض الصور وعدد النتائج
   const dogImages = screen.getAllByRole("img");
   expect(dogImages).toHaveLength(2);
   expect(screen.getByText(/2 Results/i)).toBeInTheDocument();
   expect(dogImages[0]).toHaveAccessibleName("cattledog 1 of 2");
   expect(dogImages[1]).toHaveAccessibleName("cattledog 2 of 2");
})

احفظ التغييرات التي تم إجراؤها في src/App.test.js.

عندما تستعرض الاختبارات الخاصة بك، سيكون الإخراج النهائي في الطرفية الآن على النحو التالي:

Output
PASS src/App.test.js ✓ renders the landing page (273 ms) ✓ should be able to search and display dog image results (123 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 4.916 s Ran all test suites related to changed files. Watch Usage: Press w to show more.

في هذه الخطوة النهائية، قمت بإضافة اختبار يتحقق من وظائف البحث والتحميل والعرض لتطبيق دليل الكلاب. بعد كتابة التأكيد النهائي، تعرف الآن أن تطبيقك يعمل.

الاستنتاج

خلال هذا البرنامج التعليمي، كتبت حالات اختبار باستخدام Jest ومكتبة اختبار React ومطابقات jest-dom. من خلال البناء التدريجي، كتبت اختبارات بناءً على كيفية تفاعل المستخدم مع واجهة المستخدم. كما تعلمت الفروق بين الاستعلامات getBy*، findBy*، وqueryBy* وكيفية اختبار الشفرات البرمجية غير المتزامنة.

لمعرفة المزيد حول المواضيع المذكورة أعلاه، تفضل بالاطلاع على الوثائق الرسمية لـ Jest، مكتبة اختبار React، و jest-dom. يمكنك أيضًا قراءة أخطاء شائعة مع مكتبة اختبار React لكنت سي. دود للتعرف على أفضل الممارسات عند العمل مع مكتبة اختبار React. للمزيد حول استخدام اختبارات الصورة الشاملة داخل تطبيق React، تحقق من كيفية كتابة اختبارات الصورة الشاملة.

Source:
https://www.digitalocean.com/community/tutorials/how-to-test-a-react-app-with-jest-and-react-testing-library