الاختبارات التي تعتمد على واجهة برمجة تطبيقات (API) حية قد تفشل لأسباب لا علاقة لها بالكود: خادم تجريبي متوقف، حد معدل من طرف ثالث، أو بيانات تغيّرت أثناء التشغيل. الحل العملي هو محاكاة واجهة برمجة التطبيقات: استبدال نقطة النهاية الحقيقية بخادم متحكم فيه يعيد الاستجابة المطلوبة بشكل ثابت وقابل للتكرار.
في هذا الدليل ستبني سير عمل عمليًا لمحاكاة API للاختبار: تحديد المخطط، إنشاء الاستجابات الوهمية، تشغيل خادم محاكاة، توجيه الاختبارات إليه، ثم تغطية مسارات الأخطاء التي يصعب إنتاجها من الخادم الحقيقي. سنستخدم مثالًا صغيرًا لواجهة إدارة الطلبات، لكن نفس الخطوات تنطبق على REST وGraphQL.
متى تستخدم محاكاة واجهة برمجة التطبيقات؟
استخدم المحاكاة عندما تريد اختبار كودك، لا الشبكة ولا استقرار خدمة خارجية.
أمثلة مناسبة:
- اختبار أن العميل يقرأ استجابة
200بشكل صحيح. - اختبار إعادة المحاولة عند
503. - اختبار رسالة خطأ واضحة عند
404. - اختبار المهلات، حدود المعدل، والاستجابات غير الصالحة.
احتفظ بالـ API الحقيقي لاختبارات العقود وطبقة صغيرة من اختبارات end-to-end. الهدف من هذه الاختبارات هو التأكد من أن المحاكاة ما زالت تطابق الواقع. القاعدة العملية:
- المحاكاة: للسرعة، العزل، وتغطية الحالات الصعبة.
- الخادم الحقيقي: لتأكيد العقد والسلوك الفعلي.
للمزيد، راجع السيناريوهات التي تؤتي فيها محاكاة واجهة برمجة التطبيقات ثمارها والفرق بين خادم المحاكاة والخادم الحقيقي.
سير العمل العملي في 5 خطوات
بغض النظر عن اللغة أو الإطار، ستتبع غالبًا هذه الخطوات:
- حدد المخطط (Schema) حتى تعكس المحاكاة شكل الاستجابة الحقيقية.
- أنشئ استجابات وهمية ثابتة أو ديناميكية.
- شغّل خادم محاكاة يقدم هذه الاستجابات عبر URL.
- وجّه اختباراتك إلى المحاكاة عبر عنوان أساسي قابل للتكوين.
-
اختبر مسارات الأخطاء مثل
404و429و500والمهلات.
سنستخدم نقطة النهاية التالية كمثال:
GET /orders/{id}
الخطوة 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
المخطط يخدم هدفين:
- يحدد شكل الاستجابة التي يجب أن تعيدها المحاكاة.
- يصبح مصدر الحقيقة الذي تحدّثه عند تغيّر العقد.
عندما يتغير العقد في الواجهة الخلفية، يجب تحديث المخطط أولًا. هذا هو الأساس الذي يجعل اختبار عقد واجهة برمجة التطبيقات قابلًا للاعتماد.
الخطوة 2: إنشاء استجابات وهمية
لديك نوعان رئيسيان من الاستجابات.
1. استجابة ثابتة
مناسبة عندما تريد نتيجة متوقعة تمامًا داخل الاختبار:
{
"id": "order_8842",
"status": "shipped",
"total": 149.99,
"items": [
{
"sku": "sku_123",
"quantity": 2
}
]
}
استخدمها عندما يكون الاختبار يعتمد على قيمة محددة:
expect(order.status).toBe('shipped');
expect(order.total).toBe(149.99);
2. استجابة ديناميكية
مناسبة لاكتشاف أخطاء لا تظهر مع حمولة واحدة ثابتة. بدلًا من تثبيت القيم، يمكن للمحاكاة توليد:
-
idمختلف لكل طلب. - قيمة
statusمن التعداد. - أرقام واقعية للحقل
total. - عناصر متعددة داخل
items.
هذا مفيد لاختبار محللات JSON، الحقول الاختيارية، القيم الطويلة، أو الحالات الحدّية.
مع Apidog، يمكن توليد نقطة نهاية وهمية من المخطط مباشرة. يقرأ Apidog أسماء الحقول وأنواعها ويعيد استجابة صالحة بدون كتابة الحمولة يدويًا.
عند كتابة الحمولات يدويًا، تجنب البيانات غير الواقعية مثل:
{
"id": "1",
"status": "ok",
"total": 0,
"items": []
}
واستخدم بيانات أقرب للإنتاج:
{
"id": "order_8842",
"status": "shipped",
"total": 149.99,
"items": [
{
"sku": "keyboard-pro",
"quantity": 1,
"price": 99.99
},
{
"sku": "usb-cable",
"quantity": 2,
"price": 25
}
]
}
الخطوة 3: تشغيل خادم المحاكاة
بعد تجهيز المخطط أو الاستجابات، تحتاج إلى تقديمها عبر خادم.
خيار 1: خادم محاكاة محلي
مناسب لاختبارات الوحدات والتكامل. يعمل بسرعة، لا يعتمد على الإنترنت، ولا يشارك حالة مع فرق أخرى.
مثال باستخدام Prism:
prism mock openapi.yaml
# Mock server listening on http://127.0.0.1:4010
بعد ذلك يمكنك استدعاء نقطة النهاية:
curl http://127.0.0.1:4010/orders/order_8842
خيار 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();
}
ثم الاختبار:
// 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);
});
في CI يمكنك ضبط المتغير قبل تشغيل الاختبارات:
API_BASE_URL=http://127.0.0.1:4010 npm test
الميزة هنا أن كود الإنتاج لا يعرف شيئًا عن المحاكاة. الاختبار فقط هو من يحدد الخادم المستهدف.
إذا كنت تشغّل الاختبارات داخل 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');
});
مثال لاختبار الاستجابة الفاشلة:
test('handles server failure', async () => {
await expect(
getOrder('order_500', BASE_URL)
).rejects.toThrow('API request failed: 500');
});
يمكن لقواعد المحاكاة المتقدمة في 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
عندما تتغير واجهة الطلبات، ستعرف أين تعدّل.
سمِّ السيناريو بوضوح
استخدم أسماء تصف الحالة:
order-shipped
order-pending
order-404
order-rate-limited
هذا يجعل الاختبار الفاشل أسهل قراءة.
احتفظ بالمحاكاة في Git
المحاكاة جزء من الاختبار، لذلك يجب مراجعتها مثل الكود:
tests/
orderClient.test.js
mocks/
orders/
order-shipped.json
openapi.yaml
استخدم استجابة أساسية مع تجاوزات
بدل نسخ نفس 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'
};
هذا يقلل التكرار ويجعل تغييرات العقد أسهل.
نفس الانضباط الذي يجعل مجموعة اختبار لأتمتة واجهة برمجة التطبيقات قابلة للصيانة ينطبق على المحاكاة أيضًا.
الحفاظ على مصداقية المحاكاة
المحاكاة قد تنحرف عن الواقع. يحدث ذلك عندما:
- تضيف الواجهة الخلفية حقلًا جديدًا.
- يتغير اسم حقل من
totalإلىamount. - يتغير تعداد
status. - تتغير بنية الخطأ.
إذا بقيت المحاكاة على الشكل القديم، ستمر الاختبارات بينما يفشل الإنتاج.
لتجنب ذلك:
- استمد المحاكاة من نفس المخطط الذي تستخدمه الواجهة الخلفية.
- شغّل اختبارات عقود دورية ضد API الحقيقي.
- راجع تغييرات المحاكاة في Pull Requests عندما يتغير العقد.
- لا تعتبر المحاكاة ملفًا مؤقتًا؛ عاملها كجزء من العقد.
مثال بسيط لفحص عقد:
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);
});
إذا فشل هذا الاختبار ضد الخادم الحقيقي، فهذا يعني أن المخطط أو المحاكاة بحاجة إلى تحديث.
إذا أردت بيئة واحدة تجمع المخطط، المحاكاة، وتشغيل الاختبارات، يمكنك تنزيل Apidog. يساعدك على إبقاء التصميم، خادم المحاكاة، ومجموعة الاختبار متزامنة. ولخيارات أوسع، راجع هذا التجميع من أدوات محاكاة واجهة برمجة تطبيقات REST.
الأسئلة المتكررة
هل يجب محاكاة واجهة برمجة التطبيقات في كل اختبار؟
لا. استخدم المحاكاة لاختبارات الوحدات والتكامل حيث تختبر كودك. احتفظ بمجموعة صغيرة من اختبارات العقود وend-to-end التي تستدعي API الحقيقي للتأكد من أن المحاكاة لا تزال تطابق الإنتاج.
ما الفرق بين الاستجابة الثابتة والديناميكية؟
الاستجابة الثابتة هي JSON لا يتغير، وهي مناسبة للتأكيدات المتوقعة. الاستجابة الديناميكية تُنشأ لكل طلب بقيم واقعية، وتساعد على كشف أخطاء قد لا تظهر مع حمولة واحدة ثابتة.
كيف أتأكد من أن المحاكاة دقيقة؟
أنشئها من نفس مخطط OpenAPI الذي تستخدمه الواجهة الخلفية، ثم شغّل اختبارات عقود مجدولة ضد API الحقيقي. إذا فشلت، حدّث المخطط والمحاكاة.
هل يمكن للمحاكاة إنتاج استجابات بطيئة أو فاشلة؟
نعم. يمكنك تكوينها لإرجاع 500، أو 429 مع ترويسة Retry-After، أو 200 بعد تأخير. هذا يسمح باختبار منطق إعادة المحاولة والمهلات بدون تعطيل خادم حقيقي.
هل أستخدم خادم محاكاة محليًا أم سحابيًا؟
استخدم المحلي لاختبارات الوحدات والتكامل لأنه أسرع وأكثر عزلة. استخدم السحابي عندما يحتاج تطبيق جوال، CI runner، أو متعاون خارجي للوصول إلى المحاكاة.
Top comments (0)