msw لـ TypeScript: دليل شامل مع أمثلة عملية
ما هي مكتبة msw ولماذا تحتاجها؟
Mock Service Worker (msw) هي مكتبة لاعتراض ومحاكاة طلبات الشبكة على مستوى Service Worker API. على عكس الحلول التقليدية (مثل axios-mock-adapter أو nock)، تعمل msw على مستوى المتصفح أو Node.js، حيث تعترض طلبات HTTP الحقيقية وتعيد ردودًا محددة مسبقًا. يتيح لك ذلك اختبار التطبيقات دون الحاجة إلى تشغيل خادم حقيقي أو تعديل كود التطبيق.
الهدف الرئيسي لـ msw هو توفير أداة موحدة لمحاكاة API في اختبارات المتصفح (باستخدام Service Worker حقيقي) وفي اختبارات الخادم (Node.js). المكتبة مفيدة بشكل خاص للمطورين الذين يستخدمون TypeScript، حيث توفر كتابة صارمة للأنواع (strict typing) لجميع الطلبات والردود. تعتبر msw مثالية لاختبارات الوحدة، واختبارات التكامل، وكذلك لتطوير الواجهة الأمامية بمعزل عن الخلفية.
الميزة الرئيسية لـ msw هي أن المحاكاة تعمل على مستوى الشبكة وليس على مستوى الكود. هذا يعني أن أي مكتبات لتنفيذ طلبات HTTP (مثل fetch، axios، graphql-request وغيرها) سيتم اعتراضها تلقائيًا دون أي إعدادات إضافية. تدعم المكتبة REST API و GraphQL، كما تسمح بمعالجة اتصالات WebSocket (عبر الإضافات).
التثبيت
لتثبيت msw في مشروع TypeScript، قم بتنفيذ أحد الأوامر التالية:
# npm
npm install msw --save-dev
# yarn
yarn add msw --dev
# pnpm
pnpm add msw --save-dev
بعد التثبيت، يجب تهيئة Service Worker للاستخدام في المتصفح:
npx msw init public/ --save
للاستخدام على الخادم (Node.js)، لا حاجة لتهيئة إضافية.
بداية سريعة — مثال عملي بسيط
لنقم بإنشاء محاكاة بسيطة لـ REST API. المثال يتضمن تعريف معالج الطلبات، إعداد الخادم، وطلب اختباري.
// handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/user', () => {
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3f',
firstName: 'أحمد',
lastName: 'المالكي',
});
}),
];
// server.ts (لـ Node.js)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// test.ts
import { server } from './server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('يجب جلب بيانات المستخدم', async () => {
const response = await fetch('https://api.example.com/user');
const data = await response.json();
expect(data.firstName).toBe('أحمد');
});
الدوال والطرق الأساسية
1. http.get()
التوقيع: http.get(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)
الوصف: ينشئ معالجًا لطلبات GET على عنوان URL محدد. يدعم السلاسل النصية الدقيقة، التعبيرات المنتظمة، والقوالب ذات المعاملات.
import { http, HttpResponse } from 'msw';
export const handler = http.get('https://api.example.com/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({ id, name: 'مستخدم ' + id });
});
2. http.post()
التوقيع: http.post(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)
الوصف: ينشئ معالجًا لطلبات POST. يسمح بقراءة محتوى الطلب عبر request.json().
import { http, HttpResponse } from 'msw';
export const handler = http.post('https://api.example.com/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ message: 'تم إنشاء المستخدم', data: body }, { status: 201 });
});
3. http.put()
التوقيع: http.put(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)
الوصف: ينشئ معالجًا لطلبات PUT لتحديث الموارد. مشابه لـ POST في التعامل مع جسم الطلب.
import { http, HttpResponse } from 'msw';
export const handler = http.put('https://api.example.com/users/:id', async ({ params, request }) => {
const { id } = params;
const updates = await request.json();
return HttpResponse.json({ id, ...updates, updatedAt: new Date().toISOString() });
});
4. http.delete()
التوقيع: http.delete(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)
الوصف: ينشئ معالجًا لطلبات DELETE. يُستخدم لحذف الموارد.
import { http, HttpResponse } from 'msw';
export const handler = http.delete('https://api.example.com/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({ message: `تم حذف المستخدم ${id}` });
});
5. http.all()
التوقيع: http.all(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)
الوصف: ينشئ معالجًا يلتقط جميع أنواع طرق HTTP (GET, POST, PUT, DELETE, PATCH, إلخ) لعنوان URL محدد.
import { http, HttpResponse } from 'msw';
export const handler = http.all('https://api.example.com/*', ({ request }) => {
return HttpResponse.json({
method: request.method,
url: request.url,
message: 'تم اعتراض الطلب'
});
});
6. HttpResponse.json()
التوقيع: HttpResponse.json(body: any, init?: ResponseInit)
الوصف: دالة مساعدة لإنشاء استجابة JSON. تقوم تلقائيًا بتعيين رأس Content-Type إلى application/json.
import { HttpResponse } from 'msw';
// استجابة JSON بسيطة
const response1 = HttpResponse.json({ success: true });
// استجابة JSON مع رؤوس مخصصة وحالة
const response2 = HttpResponse.json(
{ error: 'غير مصرح' },
{ status: 401, headers: { 'X-Custom-Header': 'قيمة' } }
);
7. HttpResponse.text()
التوقيع: HttpResponse.text(body: string, init?: ResponseInit)
الوصف: دالة مساعدة لإنشاء استجابة نصية عادية.
import { HttpResponse } from 'msw';
const response = HttpResponse.text('مرحبًا بالعالم!');
8. delay()
التوقيع: delay(duration: number | 'realistic')
الوصف: دالة لإضافة تأخير زمني للاستجابة. يمكن استخدام قيمة رقمية (بالملي ثانية) أو القيمة 'realistic' لمحاكاة تأخير الشبكة الحقيقي.
import { http, HttpResponse, delay } from 'msw';
export const handler = http.get('https://api.example.com/slow', async () => {
await delay(1000); // تأخير لمدة ثانية واحدة
return HttpResponse.json({ message: 'تم بعد التأخير' });
});
أمثلة متقدمة
مثال 1: محاكاة أخطاء الشبكة
import { http, HttpResponse } from 'msw';
export const errorHandler = http.get('https://api.example.com/error', () => {
return new HttpResponse(null, { status: 500, statusText: 'خطأ داخلي في الخادم' });
});
مثال 2: استخدام معاملات ديناميكية
import { http, HttpResponse } from 'msw';
const users = [
{ id: 1, name: 'سارة' },
{ id: 2, name: 'خالد' },
];
export const handlers = [
http.get('https://api.example.com/users', () => {
return HttpResponse.json(users);
}),
http.get('https://api.example.com/users/:id', ({ params }) => {
const user = users.find(u => u.id === Number(params.id));
if (!user) {
return new HttpResponse(null, { status: 404 });
}
return HttpResponse.json(user);
}),
];
مثال 3: محاكاة GraphQL
import { graphql, HttpResponse } from 'msw';
export const handlers = [
graphql.query('GetUser', ({ variables }) => {
return HttpResponse.json({
data: {
user: {
id: variables.id,
name: 'مستخدم تجريبي',
email: 'test@example.com',
},
},
});
}),
];
الخاتمة
تعتبر مكتبة msw أداة قوية ومرنة لمحاكاة طلبات الشبكة في تطبيقات TypeScript. سواء كنت تختبر تطبيقات React أو Vue أو Angular أو حتى تطبيقات Node.js الخلفية، فإن msw توفر حلاً موحدًا يعمل عبر جميع البيئات. مع دعمها الكامل لـ TypeScript، يمكنك الاستفادة من الكتابة الصارمة للأنواع لتقليل الأخطاء وتحسين تجربة التطوير.
للمزيد من المعلومات، تفضل بزيارة الموقع الرسمي للمكتبة.