Kodokon kodokon.com

اختبار واجهة برمجة التطبيقات (API): node:test، وfetch، وقاعدة بيانات للاختبار

اختبر مساراتك من الطرف إلى الطرف باستخدام node:test وfetch على منفذ عابر، مع حصول كل اختبار على قاعدة بيانات SQLite خاصة به في الذاكرة.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

يأتي Node 20 مزوّدًا بكل ما تحتاجه: node:test كمُشغِّل، وnode:assert/strict للتوكيدات (assertions)، وfetch العام كعميل HTTP. صفر تبعيات للاختبار. وهنا تؤتي بنية الدرس الأول ثمارها: تقبل createApp({ db }) قاعدة بيانات محقونة، فيمكن لكل اختبار أن يركّب واجهة برمجة تطبيقات (API) كاملة ومعزولة في بضعة أجزاء من الثانية (milliseconds).

BASH
node --test
node --test --watch
يكتشف المُشغِّل ملفات *.test.js في المشروع من تلقاء نفسه.

لاختبار المسارات من الطرف إلى الطرف، لا حاجة إلى محاكاة (mock) Express: ركِّب خادمًا حقيقيًا على المنفذ 0 - يخصص النظام منفذًا حرًا - ثم استهدفه بـ fetch. أنت تختبر بالضبط ما سيراه عميل حقيقي: التوجيه، والبرمجيات الوسيطة، والتسلسل، ورموز الحالة. إنها روح supertest، دون التبعية.

JAVASCRIPT
import { once } from 'node:events';

export async function startTestServer(app) {
  const server = app.listen(0);
  await once(server, 'listening');
  const { port } = server.address();
  const url = `http://127.0.0.1:${port}`;
  const close = () =>
    new Promise((resolve) => {
      server.close(resolve);
    });
  return { url, close };
}
helpers.js: خادم حقيقي على منفذ يخصصه نظام التشغيل.

قاعدة بيانات الاختبار: SQLite في الذاكرة (:memory:). يبني كل اختبار قاعدة بياناته الجديدة الفارغة الخاصة به عبر createDb، التي تطبّق المخطط، ثم يتخلّص منها عند الخروج - دون بيانات مشتركة بين الاختبارات، ودون سكربت تنظيف. وتضمن t.after إغلاق الخادم حتى عند فشل توكيد.

JAVASCRIPT
import test from 'node:test';
import assert from 'node:assert/strict';
import { createDb } from '../src/db/connection.js';
import { createApp } from '../src/app.js';
import { startTestServer } from './helpers.js';

test('POST /api/users creates a user', async (t) => {
  const db = createDb(':memory:');
  const app = createApp({ db });
  const server = await startTestServer(app);
  t.after(() => server.close());

  const res = await fetch(`${server.url}/api/users`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      email: 'ada@example.com',
      name: 'Ada',
    }),
  });

  assert.equal(res.status, 201);
  const body = await res.json();
  assert.equal(body.email, 'ada@example.com');
});
اختبار من الطرف إلى الطرف: مكدس HTTP حقيقي، وقاعدة بيانات للاستعمال ثم الإتلاف.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. لماذا نُشغِّل خادم الاختبار باستخدام app.listen(0)؟
    • المنفذ 0 يُعطِّل الشبكة ويُسرِّع الاختبارات
    • يخصص النظام منفذًا حرًا: فالاختبارات المتوازية لا تتنازع أبدًا على المنفذ نفسه
    • إنه المنفذ الوحيد المسموح به خارج الإنتاج
    • المنفذ 0 يُجبِر Express على الدخول في وضع الاختبار
  2. ما الميزة الحاسمة لقاعدة بيانات :memory: تُنشأ في كل اختبار؟
    • تحفظ البيانات بين عمليات التشغيل للمقارنة
    • يبدأ كل اختبار من حالة فارغة ومعزولة، دون سكربت تنظيف ودون تداخل بين الاختبارات
    • تُعطِّل قيود SQL لتبسيط عمليات الإدراج
  3. لماذا نُغلق الخادم في t.after بدلًا من نهاية جسم الاختبار؟
    • تعمل t.after حتى لو فشل توكيد، ما يتجنّب خوادم يتيمة تمنع العملية من الخروج
    • تعمل t.after قبل الاختبار، ما يُهيّئ الخادم
    • إنه أمر أسلوبي بحت، والسلوك متطابق