اختبار دون Jest: كيف نستخدم مشغّل اختبارات Node.js المدمج
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أبرز النقاط
مشغّل الاختبارات في Node.js مدمج داخل وحدة node:test ويُشغّل بالأمر node --test. لا يحتاج إلى أي اعتمادية أو ملف إعداد، وهو مستقر منذ Node.js 20، ولذلك نعتمده في فريقنا كخيار افتراضي لكود الواجهة الخلفية والمكتبات.
تأتي التوكيدات من وحدة node:assert المدمجة. نستورد node:assert/strict حتى تستخدم assert.equal المقارنة الصارمة === بدلاً من المقارنة المرنة.
يوفّر كائن mock من node:test الدوال mock.fn() وmock.method() وmock.timers دون مكتبة محاكاة منفصلة. أما محاكاة وحدات ES الكاملة عبر mock.module() فما زالت تجريبية.
بدمجه مع تجريد الأنواع، يشغّل node --test مع نمط مثل src/**/*.test.ts اختبارات TypeScript دون أي خطوة بناء على Node.js 22.18 وما بعده وعلى Node.js 24.
تتوفّر تغطية الكود خلف الراية --experimental-test-coverage، ويُختار تنسيق المخرجات عبر --test-reporter الذي يدعم spec وtap وdot وjunit وlcov.
هل يمكن لـ Node.js تشغيل الاختبارات دون Jest أو Vitest؟
نعم، ودون أي اعتماديات. المشغّل هو وحدة node:test الأساسية التي تُستدعى بالأمر node --test. ظهر تجريبياً في Node.js 18 وأصبح مستقراً في Node.js 20. يكتشف ملفات الاختبار تلقائياً وفق أنماط مثل *.test.* أو الملفات داخل مجلد test، ويشغّل كل ملف في عملية فرعية مستقلة لعزله، ويعرض النتائج بتنسيق قائم على TAP.
يستورد الاختبار البسيط test من node:test وassert من node:assert/strict، ثم يؤكّد أن دالة تُعيد القيمة المتوقعة. وتشغيل node --test ينفّذ المجموعة كاملة. لا يوجد ملف jest.config ولا خط تحويل ولا ts-jest؛ فملف الاختبار هو الإعداد بأكمله. وبالنسبة لسكربتاتنا ومكتباتنا الداخلية، أزال ذلك فئة كاملة من الأدوات.
كيف نبني الاختبارات باستخدام node:test؟
تصدّر وحدة node:test الدالة test إضافة إلى describe وit للتجميع بأسلوب BDD، والخطافات الأربعة before وafter وbeforeEach وafterEach. وتمنح كتل describe المتداخلة والاختبارات الفرعية التنظيم نفسه الذي يقدّمه Jest، ما يجعل نقل بنية مجموعة قائمة أمراً آلياً.
نركّز اختباراً واحداً عبر خيار only مع الراية --test-only، أو نرشّح بالاسم عبر --test-name-pattern. ويستخدم التخطي والمهام المؤجّلة الخيارين skip وtodo. ولا يتطلب أي من ذلك إضافة خارجية.
كيف نحاكي الدوال والطرائق والمؤقتات؟
المحاكاة مدمجة عبر كائن mock المُصدّر من node:test، فلا حاجة لتثبيت مكافئ لـ jest.fn. تنشئ mock.fn() جاسوساً تُسجّل استدعاءاته في مصفوفة mock.calls وطريقة mock.callCount().
تستبدل mock.method(object, name) طريقة حقيقية بجاسوس وتعيدها تلقائياً عند استخدام مساعد mock في سياق الاختبار. وتأتي المؤقتات الوهمية من mock.timers.enable، وبعدها تُقدّم mock.timers.tick الزمن بشكل حتمي. وتوجد محاكاة وحدات ES الكاملة بصيغة mock.module() لكنها ما زالت تجريبية، لذا نعتمد على حقن الاعتماديات — تمرير المتعاونين كوسائط — وهو ما لا يحتاج إلى أي اعتراض للوحدات.
هل نشغّل اختبارات TypeScript دون خطوة بناء؟
نعم، على Node.js 22.18 وما بعده وعلى Node.js 24، لأن Node.js يجرّد تعليقات الأنواع عند التحميل. نوجّه المشغّل إلى ملفات .ts عبر نمط فتُنفّذ مباشرة. وتنتقل قاعدتان من التنفيذ الأصلي لـ TypeScript: يجب أن تتضمن الاستيرادات النسبية امتداد .ts الصريح، وأن Node.js لا يفحص الأنواع إطلاقاً لأنه يتجاهل التعليقات دون التحقق منها.
لذلك نشغّل tsc مع راية noEmit كبوابة CI مستقلة للأنواع، ونترك node --test يتولّى السلوك الزمني. وقد أزال هذا الدمج ts-jest ومحوّل Vitest من كل مستودع سكربتات أو مكتبة نصونه.
كيف نحصل على التغطية ووضع المراقبة وتقارير CI؟
وضع المراقبة هو node --test --watch الذي يعيد تشغيل الاختبارات المتأثرة عند تغيّر الملفات. والتغطية راية لا حزمة: يطبع node --test --experimental-test-coverage جدولاً لكل ملف يبيّن تغطية الأسطر والفروع والدوال. وتُختار المُبلّغات عبر --test-reporter، ويمكن إصدار عدة مُبلّغات معاً — مثل مُبلّغ spec للبشر مع ملفي junit وlcov للـ CI.
يمكن فرض حدود التغطية عبر --test-coverage-lines و--test-coverage-branches و--test-coverage-functions على Node.js 22 وما بعده. وتُفشِل هذه الحدود التشغيل إذا نزل تحت النسبة المحددة، وهو كافٍ لضبط طلب الدمج دون الدفع لخدمة تغطية.
متى ينبغي أن نبقي على Vitest أو Jest؟
نبقي على Vitest أو Jest كلما احتاج اختبار إلى بيئة شبيهة بالمتصفح أو إلى محوّل بناء لا يوفّره وقت التشغيل. فمشغّل اختبارات Node.js لا يملك DOM، ولا يحوّل JSX أو TSX، ودعمه للّقطات أحدث وأقل نضجاً من Vitest.
التقسيم الذي نتّبعه عملياً: كود الواجهة الخلفية والمكتبات الذي يعمل على Node.js يأخذ المشغّل المدمج بلا اعتماديات ومع TypeScript أصلي؛ وأي شيء يعرض مكوّناً أو يستدعي كائن document يبقى على Vitest مع jsdom ومحوّل JSX. فـ Node.js أفضل للسكربتات والمكتبات ومنطق الواجهة الخلفية، بينما Vitest وJest أفضل للواجهة الأمامية واختبارات المكوّنات ومجموعات التطبيقات الكبيرة.
الأسئلة الشائعة
هل مشغّل اختبارات Node.js جاهز للإنتاج؟ نعم. المشغّل الأساسي وواجهة node:test مستقران منذ Node.js 20. أما محاكاة الوحدات والتغطية فما زالتا خلف رايات تجريبية، لكن نواة test وassert وmock مستقرة.
هل نحتاج ملف إعداد؟ لا. يكتشف node --test ملفات الاختبار تلقائياً ولا يحتاج إعداداً. تُمرّر الخيارات كرايات سطر أوامر بدلاً من ملف jest.config.
أي مكتبة توكيد يستخدم؟ وحدة node:assert المدمجة. نستورد node:assert/strict لتكون فحوص المساواة صارمة افتراضياً، وتبقى أي مكتبة توكيد خارجية صالحة إن فُضّلت.
هل يمكنه اختبار مكوّنات React؟ ليس جيداً. فهو لا يملك بيئة DOM ولا يحوّل JSX. لاختبارات مكوّنات React نستخدم Vitest أو Jest مع jsdom، ونحتفظ بمشغّل Node.js للمنطق والأدوات وكود الواجهة الخلفية.
كيف يحاكي وحدات ES؟ عبر mock.module() التي ما زالت تجريبية. للكود المستقر نفضّل حقن الاعتماديات بتمرير المتعاونين كوسائط، وهو ما لا يحتاج إلى محاكاة وحدات إطلاقاً.
قراءات إضافية
أدوات المطوّرين
تيربوريبو في مستودع أحادي حقيقي: ملاحظات ميدانية عن فشل التخزين المؤقت وتسميم متغيرات NEXT_PUBLIC ومفتاح tasks الذي حلّ محل pipeline
تيربوريبو أداة لتشغيل المهام تحسب بصمة لمدخلات كل سكربت وتعيد تشغيل المخرجات المخزّنة بدل تنفيذ العمل من جديد. نقلنا مستودعاً أحادياً يضم تطبيقين إلى تيربوريبو 2، وهذه ملاحظاتنا عن سبب فشل التخزين المؤقت، وعن مرة شحن فيها التخزين المؤقت روابط بيئة الاختبار إلى الإنتاج، وعن سبب بناء --affected لكل شيء بصمت مع نسخة git ضحلة في التكامل المستمر.
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.