اختبار TypeScript: من الوحدة إلى E2E — دليل شامل
\nتطوير الويب الحديث لا يمكن تصوره بدون اختبار عالي الجودة. TypeScript، كطبقة فوقية لـ JavaScript، يوفر أدوات قوية للكتابة (Typing)، لكنه لا يحل مشكلة الأخطاء المنطقية. في هذه المقالة، سنستعرض ثلاثة مستويات من الاختبار: unit (اختبار الوحدة)، integration (اختبار التكامل)، وe2e (اختبار شامل). ستتعلم كيفية إعداد البيئة، وكتابة اختبارات فعالة بلغة TypeScript، ودمجها في خط أنابيب CI/CD.
\n\n1. اختبار الوحدة: أساس هرم الاختبار
\nتتحقق اختبارات الوحدة من الوحدات المنعزلة (الدوال، الفئات، الطرق) دون الاعتماد على التبعيات الخارجية. أفضل خيار لـ TypeScript هو Jest — فهو يدعم TypeScript بشكل افتراضي ويوفر واجهة برمجة تطبيقات غنية للـ mocks والـ assertions.
\n\nإعداد Jest لـ TypeScript
\nnpm install --save-dev jest @types/jest ts-jest\nnpx ts-jest config:init\nيجب أن يحتوي ملف jest.config.ts على:
export default {\n preset: 'ts-jest',\n testEnvironment: 'node',\n roots: ['<rootDir>/src'],\n testMatch: ['**/__tests__/**/*.test.ts']\n};\n\nمثال لاختبار وحدة لدالة
\nلنفترض أن لدينا دالة لحساب الخصم:
\n// src/utils/discount.ts\nexport function calculateDiscount(price: number, percent: number): number {\n if (percent < 0 || percent > 100) throw new Error('Invalid percent');\n return price - (price * percent) / 100;\n}\nاختبارها:
\n// src/__tests__/discount.test.ts\nimport { calculateDiscount } from '../utils/discount';\n\ndescribe('calculateDiscount', () => {\n it('يجب أن يعيد 80 لسعر 100 وخصم 20%', () => {\n expect(calculateDiscount(100, 20)).toBe(80);\n });\n\n it('يجب أن يرمي خطأ عند نسبة مئوية غير صحيحة', () => {\n expect(() => calculateDiscount(100, 150)).toThrow('Invalid percent');\n });\n\n it('يجب أن يعيد 0 عند خصم 100%', () => {\n expect(calculateDiscount(50, 100)).toBe(0);\n });\n});\n\nالعمل مع الـ Mocks
\nلعزل الاختبارات، استخدم الـ mocks. مثال مع خدمة تعتمد على قاعدة البيانات:
\n// src/services/user.service.ts\nimport { db } from '../database';\n\nexport class UserService {\n async getUser(id: number) {\n const user = await db.findUser(id);\n if (!user) throw new Error('User not found');\n return user;\n }\n}\n// src/__tests__/user.service.test.ts\nimport { UserService } from '../services/user.service';\nimport { db } from '../database';\n\njest.mock('../database');\n\ndescribe('UserService', () => {\n it('يجب أن يعيد المستخدم', async () => {\n const mockUser = { id: 1, name: 'Alice' };\n (db.findUser as jest.Mock).mockResolvedValue(mockUser);\n\n const service = new UserService();\n const result = await service.getUser(1);\n expect(result).toEqual(mockUser);\n });\n});\n\n2. اختبار التكامل: التحقق من تفاعل المكونات
\nتتحقق اختبارات التكامل من كيفية عمل الوحدات معًا: مسارات API، قواعد البيانات، الخدمات الخارجية. لتطبيقات TypeScript على Node.js، غالبًا ما يُستخدم Supertest مع Jest.
\n\nإعداد اختبار التكامل لـ Express/NestJS
\nnpm install --save-dev supertest @types/supertest\nمثال لاختبار REST API (NestJS):
\n// src/__tests__/app.integration.test.ts\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { AppModule } from '../app.module';\n\ndescribe('AppController (e2e)', () => {\n let app: INestApplication;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n a