DEV Community

Cover image for كيفية محاكاة واجهة برمجة تطبيقات للاختبار: دليل عملي
Yusuf Khalidd
Yusuf Khalidd

Posted on • Originally published at apidog.com

كيفية محاكاة واجهة برمجة تطبيقات للاختبار: دليل عملي

الاختبارات التي تعتمد على واجهة برمجة تطبيقات (API) حية قد تفشل لأسباب لا علاقة لها بالكود: خادم تجريبي متوقف، حد معدل من طرف ثالث، أو بيانات تغيّرت أثناء التشغيل. الحل العملي هو محاكاة واجهة برمجة التطبيقات: استبدال نقطة النهاية الحقيقية بخادم متحكم فيه يعيد الاستجابة المطلوبة بشكل ثابت وقابل للتكرار.

جرّب Apidog اليوم

في هذا الدليل ستبني سير عمل عمليًا لمحاكاة API للاختبار: تحديد المخطط، إنشاء الاستجابات الوهمية، تشغيل خادم محاكاة، توجيه الاختبارات إليه، ثم تغطية مسارات الأخطاء التي يصعب إنتاجها من الخادم الحقيقي. سنستخدم مثالًا صغيرًا لواجهة إدارة الطلبات، لكن نفس الخطوات تنطبق على REST وGraphQL.

متى تستخدم محاكاة واجهة برمجة التطبيقات؟

استخدم المحاكاة عندما تريد اختبار كودك، لا الشبكة ولا استقرار خدمة خارجية.

أمثلة مناسبة:

  • اختبار أن العميل يقرأ استجابة 200 بشكل صحيح.
  • اختبار إعادة المحاولة عند 503.
  • اختبار رسالة خطأ واضحة عند 404.
  • اختبار المهلات، حدود المعدل، والاستجابات غير الصالحة.

احتفظ بالـ API الحقيقي لاختبارات العقود وطبقة صغيرة من اختبارات end-to-end. الهدف من هذه الاختبارات هو التأكد من أن المحاكاة ما زالت تطابق الواقع. القاعدة العملية:

  • المحاكاة: للسرعة، العزل، وتغطية الحالات الصعبة.
  • الخادم الحقيقي: لتأكيد العقد والسلوك الفعلي.

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

سير العمل العملي في 5 خطوات

بغض النظر عن اللغة أو الإطار، ستتبع غالبًا هذه الخطوات:

  1. حدد المخطط (Schema) حتى تعكس المحاكاة شكل الاستجابة الحقيقية.
  2. أنشئ استجابات وهمية ثابتة أو ديناميكية.
  3. شغّل خادم محاكاة يقدم هذه الاستجابات عبر URL.
  4. وجّه اختباراتك إلى المحاكاة عبر عنوان أساسي قابل للتكوين.
  5. اختبر مسارات الأخطاء مثل 404 و429 و500 والمهلات.

سنستخدم نقطة النهاية التالية كمثال:

GET /orders/{id}
Enter fullscreen mode Exit fullscreen mode

الخطوة 1: تحديد المخطط

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

مثال مختصر لـ GET /orders/{id}:

paths:
  /orders/{id}:
    get:
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Order found
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - status
                  - total
                  - items
                properties:
                  id:
                    type: string
                    example: order_8842
                  status:
                    type: string
                    enum: [pending, shipped, delivered]
                    example: shipped
                  total:
                    type: number
                    example: 149.99
                  items:
                    type: array
                    items:
                      type: object
        '404':
          description: Order not found
Enter fullscreen mode Exit fullscreen mode

المخطط يخدم هدفين:

  • يحدد شكل الاستجابة التي يجب أن تعيدها المحاكاة.
  • يصبح مصدر الحقيقة الذي تحدّثه عند تغيّر العقد.

عندما يتغير العقد في الواجهة الخلفية، يجب تحديث المخطط أولًا. هذا هو الأساس الذي يجعل اختبار عقد واجهة برمجة التطبيقات قابلًا للاعتماد.

الخطوة 2: إنشاء استجابات وهمية

لديك نوعان رئيسيان من الاستجابات.

1. استجابة ثابتة

مناسبة عندما تريد نتيجة متوقعة تمامًا داخل الاختبار:

{
  "id": "order_8842",
  "status": "shipped",
  "total": 149.99,
  "items": [
    {
      "sku": "sku_123",
      "quantity": 2
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

استخدمها عندما يكون الاختبار يعتمد على قيمة محددة:

expect(order.status).toBe('shipped');
expect(order.total).toBe(149.99);
Enter fullscreen mode Exit fullscreen mode

2. استجابة ديناميكية

مناسبة لاكتشاف أخطاء لا تظهر مع حمولة واحدة ثابتة. بدلًا من تثبيت القيم، يمكن للمحاكاة توليد:

  • id مختلف لكل طلب.
  • قيمة status من التعداد.
  • أرقام واقعية للحقل total.
  • عناصر متعددة داخل items.

هذا مفيد لاختبار محللات JSON، الحقول الاختيارية، القيم الطويلة، أو الحالات الحدّية.

مع Apidog، يمكن توليد نقطة نهاية وهمية من المخطط مباشرة. يقرأ Apidog أسماء الحقول وأنواعها ويعيد استجابة صالحة بدون كتابة الحمولة يدويًا.

عند كتابة الحمولات يدويًا، تجنب البيانات غير الواقعية مثل:

{
  "id": "1",
  "status": "ok",
  "total": 0,
  "items": []
}
Enter fullscreen mode Exit fullscreen mode

واستخدم بيانات أقرب للإنتاج:

{
  "id": "order_8842",
  "status": "shipped",
  "total": 149.99,
  "items": [
    {
      "sku": "keyboard-pro",
      "quantity": 1,
      "price": 99.99
    },
    {
      "sku": "usb-cable",
      "quantity": 2,
      "price": 25
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

الخطوة 3: تشغيل خادم المحاكاة

بعد تجهيز المخطط أو الاستجابات، تحتاج إلى تقديمها عبر خادم.

خيار 1: خادم محاكاة محلي

مناسب لاختبارات الوحدات والتكامل. يعمل بسرعة، لا يعتمد على الإنترنت، ولا يشارك حالة مع فرق أخرى.

مثال باستخدام Prism:

prism mock openapi.yaml
# Mock server listening on http://127.0.0.1:4010
Enter fullscreen mode Exit fullscreen mode

بعد ذلك يمكنك استدعاء نقطة النهاية:

curl http://127.0.0.1:4010/orders/order_8842
Enter fullscreen mode Exit fullscreen mode

خيار 2: خادم محاكاة سحابي

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

  • تطبيق جوال على جهاز حقيقي.
  • مشغل CI لا يستطيع الوصول إلى جهازك.
  • زميل أو متعاون خارجي.

يوفر Apidog عنوان URL لمحاكاة سحابية لكل مشروع، بحيث يمكن للجميع استدعاء نفس نقاط النهاية المستضافة.

للاختبارات الآلية اليومية، ابدأ بالمحلي. للسرديات، العروض، أو الاختبار عبر الأجهزة، استخدم السحابي.

الخطوة 4: توجيه الاختبارات إلى المحاكاة

لا تضع عنوان الخادم داخل الكود مباشرة. اجعل عنوان الـ API الأساسي قابلًا للتكوين عبر متغير بيئة.

مثال عميل بسيط:

// orderClient.js
export async function getOrder(id, baseUrl) {
  const response = await fetch(`${baseUrl}/orders/${id}`);

  if (response.status === 404) {
    throw new Error('Order not found');
  }

  if (!response.ok) {
    throw new Error(`API request failed: ${response.status}`);
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

ثم الاختبار:

// orderClient.test.js
import { getOrder } from './orderClient.js';

const BASE_URL = process.env.API_BASE_URL || 'http://127.0.0.1:4010';

test('parses a shipped order', async () => {
  const order = await getOrder('order_8842', BASE_URL);

  expect(order.status).toBe('shipped');
  expect(typeof order.total).toBe('number');
  expect(Array.isArray(order.items)).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

في CI يمكنك ضبط المتغير قبل تشغيل الاختبارات:

API_BASE_URL=http://127.0.0.1:4010 npm test
Enter fullscreen mode Exit fullscreen mode

الميزة هنا أن كود الإنتاج لا يعرف شيئًا عن المحاكاة. الاختبار فقط هو من يحدد الخادم المستهدف.

إذا كنت تشغّل الاختبارات داخل pipeline، ينطبق نفس النمط عند أتمتة اختبارات واجهة برمجة التطبيقات في CI/CD.

الخطوة 5: اختبار مسارات الأخطاء

هذه هي القيمة الأكبر للمحاكاة. الخادم الحقيقي لن يعيد 500 أو 429 وقتما تحتاج، لكن خادم المحاكاة يستطيع ذلك.

ابدأ بتغطية هذه الحالات:

السيناريو استجابة المحاكاة ما يجب التحقق منه
سجل مفقود 404 العميل يرمي خطأ واضحًا
فشل الخادم 500 العميل يعيد المحاولة أو يعرض fallback
تجاوز حد المعدل 429 مع Retry-After العميل ينتظر المدة الصحيحة
استجابة بطيئة 200 بعد تأخير 5 ثوانٍ العميل يطبّق timeout
JSON غير صالح 200 مع نص معطوب العميل يفشل بأمان

مثال لاختبار 404:

test('throws clear error when order is missing', async () => {
  await expect(
    getOrder('order_404', BASE_URL)
  ).rejects.toThrow('Order not found');
});
Enter fullscreen mode Exit fullscreen mode

مثال لاختبار الاستجابة الفاشلة:

test('handles server failure', async () => {
  await expect(
    getOrder('order_500', BASE_URL)
  ).rejects.toThrow('API request failed: 500');
});
Enter fullscreen mode Exit fullscreen mode

يمكن لقواعد المحاكاة المتقدمة في Apidog إرجاع استجابات مختلفة بناءً على الطلب. مثلًا:

  • GET /orders/order_404 يعيد 404.
  • GET /orders/order_500 يعيد 500.
  • أي معرف آخر يعيد 200.

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

تنظيم المحاكاة مع نمو مجموعة الاختبار

محاكاة نقطة نهاية واحدة سهلة. المشكلة تبدأ عندما يصبح لديك عشرات أو مئات المحاكيات. اتبع هذه القواعد:

اجمع المحاكاة حسب الخدمة

لا تنظّمها حسب الاختبار، بل حسب الخدمة الحقيقية:

mocks/
  orders/
    order-shipped.json
    order-not-found.json
    order-rate-limited.json
  payments/
    payment-authorized.json
    payment-declined.json
Enter fullscreen mode Exit fullscreen mode

عندما تتغير واجهة الطلبات، ستعرف أين تعدّل.

سمِّ السيناريو بوضوح

استخدم أسماء تصف الحالة:

order-shipped
order-pending
order-404
order-rate-limited
Enter fullscreen mode Exit fullscreen mode

هذا يجعل الاختبار الفاشل أسهل قراءة.

احتفظ بالمحاكاة في Git

المحاكاة جزء من الاختبار، لذلك يجب مراجعتها مثل الكود:

tests/
  orderClient.test.js
mocks/
  orders/
    order-shipped.json
openapi.yaml
Enter fullscreen mode Exit fullscreen mode

استخدم استجابة أساسية مع تجاوزات

بدل نسخ نفس JSON في كل اختبار، عرّف استجابة أساسية وغيّر الحقل المطلوب فقط:

const baseOrder = {
  id: 'order_8842',
  status: 'shipped',
  total: 149.99,
  items: [
    { sku: 'keyboard-pro', quantity: 1, price: 99.99 }
  ]
};

const pendingOrder = {
  ...baseOrder,
  status: 'pending'
};
Enter fullscreen mode Exit fullscreen mode

هذا يقلل التكرار ويجعل تغييرات العقد أسهل.

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

الحفاظ على مصداقية المحاكاة

المحاكاة قد تنحرف عن الواقع. يحدث ذلك عندما:

  • تضيف الواجهة الخلفية حقلًا جديدًا.
  • يتغير اسم حقل من total إلى amount.
  • يتغير تعداد status.
  • تتغير بنية الخطأ.

إذا بقيت المحاكاة على الشكل القديم، ستمر الاختبارات بينما يفشل الإنتاج.

لتجنب ذلك:

  1. استمد المحاكاة من نفس المخطط الذي تستخدمه الواجهة الخلفية.
  2. شغّل اختبارات عقود دورية ضد API الحقيقي.
  3. راجع تغييرات المحاكاة في Pull Requests عندما يتغير العقد.
  4. لا تعتبر المحاكاة ملفًا مؤقتًا؛ عاملها كجزء من العقد.

مثال بسيط لفحص عقد:

test('live API response matches expected order shape', async () => {
  const response = await fetch(`${LIVE_API_URL}/orders/order_8842`);
  const order = await response.json();

  expect(typeof order.id).toBe('string');
  expect(['pending', 'shipped', 'delivered']).toContain(order.status);
  expect(typeof order.total).toBe('number');
  expect(Array.isArray(order.items)).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

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

إذا أردت بيئة واحدة تجمع المخطط، المحاكاة، وتشغيل الاختبارات، يمكنك تنزيل Apidog. يساعدك على إبقاء التصميم، خادم المحاكاة، ومجموعة الاختبار متزامنة. ولخيارات أوسع، راجع هذا التجميع من أدوات محاكاة واجهة برمجة تطبيقات REST.

الأسئلة المتكررة

هل يجب محاكاة واجهة برمجة التطبيقات في كل اختبار؟

لا. استخدم المحاكاة لاختبارات الوحدات والتكامل حيث تختبر كودك. احتفظ بمجموعة صغيرة من اختبارات العقود وend-to-end التي تستدعي API الحقيقي للتأكد من أن المحاكاة لا تزال تطابق الإنتاج.

ما الفرق بين الاستجابة الثابتة والديناميكية؟

الاستجابة الثابتة هي JSON لا يتغير، وهي مناسبة للتأكيدات المتوقعة. الاستجابة الديناميكية تُنشأ لكل طلب بقيم واقعية، وتساعد على كشف أخطاء قد لا تظهر مع حمولة واحدة ثابتة.

كيف أتأكد من أن المحاكاة دقيقة؟

أنشئها من نفس مخطط OpenAPI الذي تستخدمه الواجهة الخلفية، ثم شغّل اختبارات عقود مجدولة ضد API الحقيقي. إذا فشلت، حدّث المخطط والمحاكاة.

هل يمكن للمحاكاة إنتاج استجابات بطيئة أو فاشلة؟

نعم. يمكنك تكوينها لإرجاع 500، أو 429 مع ترويسة Retry-After، أو 200 بعد تأخير. هذا يسمح باختبار منطق إعادة المحاولة والمهلات بدون تعطيل خادم حقيقي.

هل أستخدم خادم محاكاة محليًا أم سحابيًا؟

استخدم المحلي لاختبارات الوحدات والتكامل لأنه أسرع وأكثر عزلة. استخدم السحابي عندما يحتاج تطبيق جوال، CI runner، أو متعاون خارجي للوصول إلى المحاكاة.

Top comments (0)