ts-mockito لـ TypeScript: دليل شامل مع أمثلة
1. ما هو ts-mockito ولماذا تحتاجه؟
ts-mockito هي مكتبة قوية لإنشاء الموكس (mock) والستاب (stub) والسباي (spy) في لغة TypeScript. مستوحاة من مكتبة Mockito الشهيرة في Java، توفر ts-mockito واجهة برمجية مألوفة للمطورين الذين ينتقلون من Java أو Python. الهدف الرئيسي لـ ts-mockito هو تبسيط كتابة اختبارات الوحدة (unit tests) عن طريق عزل الكود المختبر عن الاعتماديات الخارجية مثل قواعد البيانات، عملاء HTTP، أنظمة الملفات، والخدمات الأخرى.
لماذا تحتاج هذا؟ في تطبيقات TypeScript الحديثة، غالبًا ما تعتمد الكلاسات والدوال على مكونات أخرى. بدون الموكس، يتحول اختبار هذه العلاقات إلى اختبار تكاملي يتطلب إعداد بيئة كاملة. تتيح لك ts-mockito إنشاء كائنات "وهمية" بسلوك محدد مسبقًا، والتحقق من أي الدوال تم استدعاؤها وبأي وسائط. هذا يجعل الاختبارات سريعة، محددة النتائج، وسهلة الصيانة.
تدعم المكتبة جميع الإمكانيات الأساسية لـ Mockito: إنشاء الموكس عبر mock()، تحديد السلوك عبر when() و thenReturn()، التحقق من الاستدعاءات عبر verify()، والعمل مع السباي عبر spy(). ts-mockito مكتوبة بالكامل مع دعم الأنواع (typed) وتتكامل بشكل ممتاز مع أطر الاختبار الشائعة مثل Jest و Mocha و Jasmine.
2. التثبيت
يتم تثبيت ts-mockito عبر npm (Node Package Manager). قم بتنفيذ الأمر التالي في جذر مشروعك:
npm install ts-mockito --save-dev
إذا كنت تستخدم yarn:
yarn add ts-mockito --dev
المكتبة لا تحتوي على تبعيات إلزامية سوى TypeScript نفسه (الإصدار 4.0+). بعد التثبيت، يمكنك استيراد الدوال اللازمة في ملفات الاختبار:
import { mock, when, verify, instance, anything, anyOfClass, deepEqual, spy } from 'ts-mockito';
ملاحظة مهمة: تستخدم ts-mockito كائنات بروكسي (proxy) لاعتراض الاستدعاءات. هذا يعني أنك تعمل دائمًا مع كائنات "مغلفة"، وليس مع نسخ حقيقية من الكلاسات.
3. بداية سريعة — مثال عملي بسيط
لنأخذ مثالًا بسيطًا: لدينا كلاس UserService يعتمد على UserRepository. نريد اختبار الدالة getUser دون الاتصال بقاعدة بيانات حقيقية.
// user-repository.ts
export class UserRepository {
async findById(id: number): Promise<{ id: number; name: string } | null> {
// استعلام حقيقي لقاعدة البيانات
return null;
}
}
// user-service.ts
export class UserService {
constructor(private repository: UserRepository) {}
async getUser(id: number): Promise<string> {
const user = await this.repository.findById(id);
if (!user) {
throw new Error('المستخدم غير موجود');
}
return `المستخدم: ${user.name}`;
}
}
// user-service.spec.ts
import { mock, when, instance, verify, anything } from 'ts-mockito';
import { UserService } from './user-service';
import { UserRepository } from './user-repository';
describe('UserService', () => {
it('يجب أن يعيد اسم المستخدم عندما يكون موجودًا', async () => {
// 1. إنشاء موك للمستودع
const mockRepository: UserRepository = mock(UserRepository);
// 2. تحديد السلوك: عند استدعاء findById(1) نعيد كائن
when(mockRepository.findById(1)).thenResolve({ id: 1, name: 'أحمد' });
// 3. الحصول على النسخة الحقيقية من الموك
const repositoryInstance = i