مقدمة: لماذا الاختبارات بدون كائنات محاكاة مثل القهوة بدون كافيين؟
\n\nتخيل: أنت تكتب تطبيقًا بلغة TypeScript، يرسل رسائل بريد إلكتروني، ويتواصل مع قاعدة بيانات، ويصل إلى واجهة برمجة تطبيقات خارجية. تقوم بتشغيل الاختبارات - وفجأة يفشل كل شيء لأن خادم البريد الإلكتروني غير متاح مؤقتًا. هل هذا مألوف؟
\n\nهنا يأتي دور كائنات المحاكاة (mock) والبيانات الثابتة (fixtures). فهي تسمح بعزل الكود عن العالم الخارجي وتجعل الاختبارات سريعة وموثوقة وقابلة للتنبؤ. في هذه المقالة باللغة العربية، سنشرح بأمثلة واقعية كيفية استخدام mock في TypeScript بشكل صحيح، وما هي أنواع البيانات الثابتة لـ TypeScript، وكيف لا نرتكب الأخطاء.
\n\nالمقالة مكتوبة لـ PythonLib - البوابة التعليمية حيث نعلم كتابة كود عالي الجودة. هيا بنا!
\n\nما هي كائنات المحاكاة والبيانات الثابتة؟
\n\nكائنات المحاكاة: ممثلون بديلون في الكود الخاص بك
\n\nكائن المحاكاة (أو mock ببساطة) هو بديل لكائن حقيقي يحاكي سلوكه. أنت تقول: "عند استدعاء طريقة sendEmail()، أعد true". ولا يتدخل الاختبار في البريد الإلكتروني الحقيقي.
\n\nفي TypeScript، يتم إنشاء الكائنات المحاكاة غالبًا باستخدام مكتبات Jest أو Vitest. وهي تسمح بالتحقق من استدعاء الطريقة، ومعرفة الوسائط التي تم تمريرها، وعدد مرات الاستدعاء.
\n\nالبيانات الثابتة: بيانات معدة مسبقًا للاختبارات
\n\nالبيانات الثابتة (fixtures) هي مجموعات بيانات معدة مسبقًا. على سبيل المثال، كائن مستخدم، قائمة طلبات، أو تكوين تطبيق. بدلاً من إنشائها يدويًا في كل مرة، تقوم بتخزين البيانات الثابتة في ملفات منفصلة.
\n\nالجمع بين mock + fixture هو معيار اختبار TypeScript في عام 2024.
\n\nتطبيق عملي: كتابة اختبارات مع كائنات محاكاة وبيانات ثابتة
\n\nإعداد البيئة
\n\nلأمثلتنا، سنستخدم Jest (على الرغم من أن كل شيء يعمل أيضًا مع Vitest). تأكد من تثبيت التبعيات:
\n\nnpm install --save-dev jest @types/jest ts-jest\n\nأضف إلى jest.config.js:
module.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n};\n\nمثال 1: محاكاة خدمة إرسال البريد الإلكتروني
\n\nلنفترض أن لدينا خدمة إشعارات:
\n\n// notificationService.ts\nexport class EmailService {\n async sendEmail(to: string, subject: string, body: string): Promise<boolean> {\n // الإرسال الحقيقي - لن نلمسه\n return true;\n }\n}\n\nexport class NotificationManager {\n constructor(private emailService: EmailService) {}\n\n async notifyUser(email: string, message: string): Promise<string> {\n const result = await this.emailService.sendEmail(email, 'إشعار', message);\n return result ? 'تم الإرسال' : 'خطأ';\n }\n}\n\nالآن لنكتب اختبارًا باستخدام كائن محاكاة:
\n\n// notificationService.test.ts\nimport { NotificationManager, EmailService } from './notificationService';\n\ndescribe('NotificationManager', () => {\n it('يجب إرسال الإشعار وإرجاع "تم الإرسال"', async () => {\n // إنشاء كائن محاكاة\n const mockEmailService: jest.Mocked<EmailService> = {\n sendEmail: jest.fn().mockResolvedValue(true),\n };\n\n const manager = new NotificationManager(mockEmailService);\n const result = await manager.notifyUser('test@test.com', 'مرحبًا!');\n\n expect(result).toBe('تم الإرسال');\n expect(mockEmailService.sendEmail).toHaveBeenCalledWith(\n 'test@test.com',\n 'إشعار',\n 'مرحبًا!'\n );\n });\n\n it('يجب إرجاع "خطأ" إذا فشل الإرسال', async () => {\n const mockEmailService: jest.Mocked<EmailService> = {\n sendEmail: jest.fn().mockResolvedValue(false),\n };\n\n const manager = new NotificationManager(mockEmailService);