إذا فشلت اختبارات Node.js الخاصة بك لأن واجهة برمجة تطبيقات (API) خارجية معطلة، أو بطيئة، أو تخضع لقيود المعدل (rate-limited)، فليس لديك مشكلة في الكود. لديك مشكلة في الاعتماديات. الحل هو محاكاة طبقة HTTP بحيث تعمل اختبارات الوحدة الخاصة بك بنفس الطريقة في كل مرة، و nock هي حزمة npm التي يلجأ إليها معظم مطوري Node. يشرح هذا الدليل ماهية nock، ويعرض مثالاً عمليًا صغيرًا، ويغطي الحالات التي يكون فيها الخادم الوهمي المشترك (shared mock server) أفضل من الاعتراض داخل العملية. للحصول على المرجع الكامل للمكتبة، يعتبر مستودع nock على GitHub هو مصدر الحقيقة.
ما هو nock؟
nock هي مكتبة لمحاكاة واعتراض طلبات HTTP لـ Node.js. تعمل عن طريق تجاوز وحدات Node http و https في وقت التشغيل، بحيث يمكن اعتراض أي طلب خارجي يقوم به الكود الخاص بك والرد عليه باستجابة جاهزة. لا يغادر شيء جهازك. لا يحدث استدعاء الشبكة أبدًا.
وهذا مهم لاختبارات الوحدة. عندما تختبر دالة تستدعي واجهة برمجة تطبيقات خارجية، لا تريد أن تضرب الخدمة الحقيقية. الاستدعاءات الحقيقية بطيئة، وتكلف مالًا، ويمكن أن تفشل لأسباب لا علاقة لها بالكود الخاص بك، وتجعل مجموعة الاختبارات الخاصة بك غير حتمية (non-deterministic). يسمح لك nock بالقول "عندما يقوم الكود الخاص بي بطلب GET إلى هذا العنوان، أرجع هذا JSON بالضبط مع رمز الحالة هذا". ثم يتحقق اختبارك من كيفية تعامل دالتك مع هذه الاستجابة.
nock مخصص لـ Node.js فقط. يتصل بمكدس HTTP الأصلي، لذا فهو يعمل مع fetch و axios و got و node-fetch وأي شيء آخر مبني على http/https. لديه ملايين التنزيلات الأسبوعية وقد كان خيارًا افتراضيًا لاختبار الواجهة الخلفية لسنوات. تقوم بتثبيته كاعتمادية تطوير لأنه يعمل فقط في الاختبارات، وليس في الإنتاج أبدًا.
مثال صغير على nock
لنفترض أن لديك دالة تجلب مستخدمًا من واجهة برمجة تطبيقات (API). هذه هي الدالة:
// user-service.js
export async function getUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return res.json();
}
الآن، هذا هو اختبار يستخدم nock لاعتراض الطلب:
// user-service.test.js
import nock from 'nock';
import { getUser } from './user-service.js';
test('returns the user when the API responds', async () => {
nock('https://api.example.com')
.get('/users/42')
.reply(200, { id: 42, name: 'Ada Lovelace' });
const user = await getUser(42);
expect(user).toEqual({ id: 42, name: 'Ada Lovelace' });
});
اقرأها من الأعلى إلى الأسفل. nock('https://api.example.com') يحدد المضيف الذي تريد اعتراضه. .get('/users/42') يطابق طريقة الطلب والمسار. .reply(200, {...}) يحدد حالة الاستجابة والجسم الذي سيعود. عندما يتم تشغيل getUser(42)، يتم استبدال استدعاء الشبكة الحقيقي وتعود استجابتك الجاهزة بدلاً من ذلك.
يمكنك محاكاة الأخطاء بنفس الطريقة، وهو الجزء الذي ينسى الناس اختباره:
test('throws when the API returns 500', async () => {
nock('https://api.example.com')
.get('/users/99')
.reply(500);
await expect(getUser(99)).rejects.toThrow('Request failed: 500');
});
اختبار المسار غير السعيد هو المكان الذي تبرز فيه قيمة المحاكاة. لا يمكنك جعل واجهة برمجة تطبيقات (API) حية ترجع خطأ 500 بشكل موثوق عند الطلب، ولكن يمكنك تزييف واحدة في سطر واحد. إذا كان هدفك الرئيسي هو محاكاة الأخطاء، فإن هذه الجولة التفصيلية حول كيفية محاكاة استجابة خطأ الخادم الداخلي 500 تتعمق أكثر.
ميزات nock المفيدة التي يجب معرفتها
تظهر بعض الطرق باستمرار بمجرد تجاوز الأساسيات.
.persist()يحافظ على بقاء المعترض (interceptor) نشطًا عبر استدعاءات متعددة. افتراضيًا، يزيل nock المعترض بعد أن يتم مطابقته مرة واحدة، وهو أمر رائع للتأكيد على أن الطلب حدث مرة واحدة بالضبط. استخدم.persist()عندما يتم استدعاء نفس نقطة النهاية بشكل متكرر في اختبار واحد..times(n)يطابق نفس الطلب عددًا محددًا من المرات قبل انتهاء صلاحية المعترض..delay(ms)يحاكي استجابة بطيئة حتى تتمكن من اختبار معالجة المهلة (timeout handling).- مطابقة التعبيرات العادية (RegExp matching) تسمح بأن يكون المضيف أو المسار نمطًا بدلاً من سلسلة ثابتة، وهو مفيد عندما تختلف المعرفات أو سلاسل الاستعلام.
nock.cleanAll()يمسح جميع المعترضات. قم بتشغيله بين الاختبارات حتى لا تتسرب المحاكيات من اختبار واحد إلى التالي.
عادة تستحق البناء مبكرًا: تأكد من استخدام جميع المحاكيات الخاصة بك بالفعل. استدعِ scope.done() على معترض (أو nock.isDone()) ويفشل الاختبار إذا لم يتم إطلاق طلب متوقع أبدًا. هذا يحول المكالمة الصامتة الفائتة إلى فشل صاخب.
متى يتوقف nock عن كونه الأداة المناسبة
تم تصميم nock لمهمة واحدة ويقوم بها على أكمل وجه: اعتراض HTTP داخل عملية Node واحدة أثناء الاختبارات الآلية. في اللحظة التي تتجاوز فيها حاجتك حدود العملية، يبدأ هذا النموذج في الإجهاد.
هذا هو القيد. تعيش محاكيات nock داخل ملف الاختبار الخاص بك، في بيئة التشغيل الخاصة بك، بلغتك. لا يمكن لمطور الواجهة الأمامية توجيه متصفحه إلى إعداد nock الخاص بك. لا يمكن لمهندس الجوال الوصول إليه من محاكي. لا يمكن لفريق ضمان الجودة (QA) إجراء فحوصات يدوية عليه. لا يمكن لمجموعة Postman الوصول إليه. المحاكاة موجودة فقط أثناء تشغيل عملية Jest أو Mocha، وفقط للكود الموجود في نفس العملية.
هذا جيد لاختبارات الوحدة وهو بالضبط ما صُمم nock من أجله. لكن العديد من المواقف الحقيقية تتطلب محاكاة تعيش في مكان يمكن للجميع الوصول إليه:
- تحتاج الواجهة الأمامية للبناء على نقطة نهاية قبل وجود الواجهة الخلفية.
- تحتاج ثلاثة فرق في مستودعات مختلفة إلى الاتفاق على نفس الاستجابات المزيفة.
- تريد عرض دمج (integration) بدون واجهة خلفية حية.
- يريد المختبر فحص الحالات الشاذة يدويًا، دون الحاجة إلى كود.
لهذه الأسباب، تحتاج إلى خادم وهمي، شيء يستمع على عنوان URL حقيقي ويرجع استجابات لأي شخص يستدعيه. إذا كنت توازن بين الفكرتين، فإن الخادم الوهمي مقابل الخادم الحقيقي والشرح الأوسع حول محاكاة واجهة برمجة التطبيقات (API mocking) كلاهما يوضحان المفاضلات.
nock مقابل خادم وهمي مستضاف (Apidog)
فكر في الأمر كطبقتين بدلاً من منافسة. يتعامل nock مع الاعتراض داخل الكود لاختبارات الوحدة. أداة مثل Apidog توفر لك خادمًا وهميًا مشتركًا مدعومًا بالمخططات لكل ما يحدث خارج عملية اختبار واحدة. تستخدم العديد من الفرق كليهما.

ينشئ Apidog خادمًا وهميًا مباشرة من تصميم واجهة برمجة التطبيقات الخاصة بك. تحدد نقطة نهاية ومخططها، ويقدم Apidog استجابات واقعية على عنوان URL حي، مع قواعد وهمية ذكية تنتج بيانات معقولة من أسماء الحقول وأنواعها. لا توجد بيئة اختبار، ولا إعداد لكل اختبار، ولا تقييد باللغة. أي شخص لديه عنوان URL يحصل على نفس الاستجابات.
| nock | خادم Apidog الوهمي | |
|---|---|---|
| أين يعمل | في عملية اختبار Node الخاصة بك | خادم مستضاف بعنوان URL حقيقي |
| الأفضل لـ | اختبارات الوحدة، محاكاة الأخطاء | الدمج، الاختبار اليدوي، العمل متعدد الفرق |
| من يمكنه الوصول إليه | الكود في نفس العملية | أي عميل يملك عنوان URL |
| الإعداد | كود في كل ملف اختبار | يتم إنشاؤه من مخطط واجهة برمجة التطبيقات (API) الخاص بك |
| اللغات | Node.js فقط | أي عميل، أي لغة |
| بيانات واقعية | تكتب كل استجابة | محاكاة ذكية من المخطط وأسماء الحقول |
| المشاركة | غير قابل للمشاركة | مشترك عبر الفريق بأكمله |
لتوضيح النطاق: Apidog لا يحل محل nock داخل اختبارات الوحدة Jest أو Mocha الخاصة بك. إذا كنت بحاجة إلى اعتراض استدعاء fetch في اختبار واحد والتحقق من النتيجة، فإن nock لا يزال هو الأداة الصحيحة. يدخل Apidog عندما تحتاج المحاكاة إلى عنوان يمكن لأشخاص وأدوات أخرى الوصول إليه. للحصول على نظرة عملية على جانب الخادم، راجع الدليل العملي لمحاكاة واجهة برمجة تطبيقات للاختبار. يمكنك تنزيل Apidog وتشغيل محاكاة من ملف OpenAPI موجود في بضع دقائق.
بدائل أخرى لـ nock
nock ليس المكتبة الوحيدة في هذا المجال، ويعتمد الاختيار الصحيح على مكان تشغيل الكود الخاص بك.
- MSW (Mock Service Worker) يعترض الطلبات على مستوى الشبكة باستخدام عامل خدمة (service worker) في المتصفح ومعترض Node على الخادم. تعمل تعريفات المحاكاة نفسها في كليهما، ولهذا السبب تعتمد الكثير من الفرق الآن عليه كخيار افتراضي لتطبيقات JavaScript الكاملة (full-stack). تشرح وثائق MSW الرسمية هذا النموذج.
- Jest manual mocks تتيح لك استبدال وحدة مثل
axiosبوحدة وهمية. هذا أبسط من nock في حالة صغيرة ولكنه يربطك بعميل HTTP واحد. يغطي الدرس التعليمي لـ Jest mocking هذا النمط. - Built-in test doubles في مشغل اختبار Node الخاص أو مكتبات مثل Sinon يمكنها تزييف الدالة التي تقوم بالاستدعاء، على الرغم من أنك تفقد مطابقة nock على مستوى HTTP.
السؤال الحاسم هو دائمًا نفسه. هل تختبر منطقًا داخل عملية واحدة، أم أنك بحاجة إلى واجهة برمجة تطبيقات وهمية تعيش على الشبكة؟ يجيب nock و MSW على الأول. يجيب الخادم الوهمي المستضاف على الثاني.
الأسئلة المتكررة
هل nock مجاني؟
نعم. nock مفتوح المصدر بموجب ترخيص MIT ومجاني للتثبيت من npm. تقوم بإضافته كاعتمادية تطوير وتستخدمه في مجموعة الاختبارات الخاصة بك بدون تكلفة.
هل يعمل nock مع fetch و axios؟
نعم. بما أن nock يعترض على طبقة Node http/https، فإنه يعمل مع أي عميل مبني على هذه الوحدات، بما في ذلك fetch الأصلي، axios، got، و node-fetch. تكتب نفس المعترض بغض النظر عن أي منها يستخدمه الكود الخاص بك.
هل يمكنني استخدام nock في المتصفح؟
لا. nock مخصص لـ Node.js فقط لأنه يقوم بتصحيح وحدات HTTP في Node، والتي لا توجد في المتصفح. للمحاكاة من جانب المتصفح، استخدم MSW أو وجه الواجهة الأمامية الخاصة بك إلى خادم وهمي مستضاف. هذا الاستعراض لواجهات برمجة التطبيقات الوهمية في JavaScript يشرح خيارات المتصفح.
ما الفرق بين nock والخادم الوهمي؟
يعترض nock الطلبات داخل عملية الاختبار الخاصة بك ولا يفتح أبدًا منفذًا حقيقيًا. يستمع الخادم الوهمي على عنوان URL حقيقي يمكن لأي عميل استدعائه. استخدم nock لاختبارات الوحدة؛ استخدم خادمًا وهميًا عندما تحتاج الواجهة الأمامية أو ضمان الجودة (QA) أو فرق أخرى إلى الوصول إلى نفس الاستجابات المزيفة.
الخلاصة
nock هو الخيار الموثوق به لمحاكاة HTTP في اختبارات وحدة Node.js. فهو يعترض الطلبات الصادرة داخل العملية، ويعيد الاستجابات التي تحددها، ويجعل مجموعة الاختبار الخاصة بك سريعة وحتمية، بما في ذلك مسارات الأخطاء التي لا يمكنك تفعيلها ضد واجهة برمجة تطبيقات حية. استمر في استخدامه لذلك.
عندما تحتاج المحاكاة إلى مغادرة ملف الاختبار الخاص بك، وعندما يتعين على الواجهة الأمامية أو ضمان الجودة (QA) أو فريق آخر الوصول إلى نفس نقطة النهاية، توجه إلى خادم وهمي مشترك بدلاً من ذلك. Apidog ينشئ واحدًا من مخطط واجهة برمجة التطبيقات الخاصة بك ويقدم بيانات واقعية على عنوان URL حي، بحيث يقوم الجميع بالبناء وفقًا لنفس العقد قبل أن تكون الواجهة الخلفية جاهزة. قم بتنزيل Apidog وحوّل مواصفات OpenAPI الخاصة بك إلى محاكاة عاملة في دقائق.
