يوفر إطار عمل 
redux-saga مجموعة من الأنماط المثيرة للاهتمام للعمل مع الآثار الجانبية ، ولكن ، مثل مطوري المؤسسات الدامياء الحقيقيين ، نحتاج إلى تغطية الكود بالكامل مع الاختبارات. دعونا معرفة كيف سنقوم باختبار الملحمات لدينا.

خذ أبسط الفرس كمثال. سيكون تدفق البيانات ومعنى التطبيق:
- المستخدم الوخزة زر.
- يتم إرسال طلب إلى الخادم ، مع العلم أن المستخدم قد ضغط زر.
- يعرض الخادم عدد النقرات التي تمت.
- تسجل الحالة عدد النقرات التي تمت.
- يتم تحديث واجهة المستخدم ، ويرى المستخدم أن عدد النقرات قد زاد.
- ...
- PROFIT.
في عملنا ، نستخدم Typescript ، لذلك ستكون جميع الأمثلة في هذه اللغة.
كما قد تكون خمنت بالفعل ، سوف ننفذ كل هذا مع 
redux-saga . إليك رمز ملف sagas بأكمله:
 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } 
في هذا المثال البسيط ، نعلن عن عملية saga 
processClick ، والتي تعالج مباشرة الإجراء و the 
watchClick ، والتي تنشئ حلقة لمعالجة 
action' .
مولدات
لذلك لدينا أبسط الملحمة. يرسل طلبًا إلى الخادم 
( call) ، ويستقبل النتيجة ويمررها إلى المخفض 
( put) الموضع 
( put) . نحن بحاجة إلى اختبار ما إذا كانت الملحمة تنقل ما تحصل عليه بالضبط من الخادم. لنبدأ.
للاختبار ، نحتاج إلى قفل مكالمة الخادم والتحقق بطريقة أو بأخرى مما إذا كان ما جاء بالضبط من الخادم قد دخل إلى المخفض.
نظرًا لأن sagas عبارة عن وظائف منشئ ، فإن الطريقة 
next() الموجودة في النموذج الأولي للمولد ، ستكون الطريقة الأكثر وضوحًا للاختبار. عند استخدام هذه الطريقة ، لدينا الفرصة لتلقي القيمة التالية من المولد ونقل القيمة إلى المولد. وبالتالي ، نخرج من الصندوق الفرصة للحصول على مكالمات مبللة. ولكن هل كل شيء وردية؟ إليك اختبار كتبته على المولدات العارية:
 it('should increment click counter (behaviour test)', () => { const saga = processClick() expect(saga.next().value).toEqual(call(ServerApi.SendClick)) expect(saga.next(10).value).toEqual(put(Actions.clickSuccess(10))) }) 
كان الاختبار موجزا ، ولكن ماذا اختبار؟ في الحقيقة ، إنه ببساطة يكرر رمز طريقة الملحمة ، أي أن أي تغيير في الملحمة سوف يضطر إلى تغيير الاختبار.
مثل هذا الاختبار لا يساعد في التنمية.
مسترجع الملحمة اختبار خطة
بعد مواجهة هذه المشكلة ، قررنا google ذلك وأدركنا فجأة أننا لسنا الوحيدين وبعيداً عن الأول. مباشرة 
في الوثائق الخاصة بـ 
redux-saga يقدم المطورون نظرة على العديد من المكتبات التي تم إنشاؤها خصيصًا لإرضاء المعجبين بالاختبار.
من القائمة المقترحة ، أخذنا مكتبة 
redux-saga-test-plan الإعادة. إليك رمز الإصدار الأول من الاختبار الذي كتبته به:
 it('should increment click counter (behaviour test with test-plan)', () => { return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 2] ]) .dispatch(Actions.click()) .call(ServerApi.SendClick) .put(Actions.clickSuccess(2)) .run() }) 
مُنشئ الاختبار في 
redux-saga-test-plan هو 
redux-saga-test-plan expectSaga ، والتي تُرجع الواجهة التي تصف الاختبار. يتم تمرير ملحمة الاختبار ( 
processClick من القائمة الأولى) إلى الوظيفة نفسها.
باستخدام طريقة التقديم ، يمكنك حظر مكالمات الخادم أو التبعيات الأخرى. 
StaticProvider' مجموعة من 
StaticProvider' ، والتي تصف الطريقة التي يجب أن تعود.
في كتلة 
Act ، لدينا طريقة واحدة - 
dispatch . يتم تمرير إجراء إلى ذلك ، والتي سوف تستجيب الملحمة.
تتكون كتلة 
assert من أساليب 
call put ، والتي تتحقق مما إذا كانت التأثيرات المقابلة قد حدثت أثناء عمل الملحمة.
كل هذا ينتهي بالطريقة 
run() . هذه الطريقة تدير الاختبار مباشرة.
مزايا هذا النهج:
- إنه يتحقق مما إذا كانت الطريقة تسمى ، وليس تسلسل المكالمات ؛
- وصف moki بوضوح الوظيفة التي تصبح رطبة وما الذي يتم إرجاعه.
ومع ذلك ، هناك عمل للقيام به:
- هناك المزيد من التعليمات البرمجية ؛
- الاختبار صعب القراءة ؛
- هذا اختبار للسلوك ، مما يعني أنه لا يزال مرتبطًا بتطبيق الملحمة.
السكتات الدماغية الأخيرة اثنين
اختبار الحالة
أولاً ، نصلح آخر اختبار: نجري اختبار حالة من اختبار للسلوك. حقيقة أن 
test-plan تسمح لك بتعيين 
state الأولية 
reducer ، والتي ينبغي أن تستجيب للآثار 
put الناتجة عن الملحمة ، سوف تساعدنا في هذا. يبدو مثل هذا:
 it('should increment click counter (state test with test-plan)', () => { const initialState = { clickCount: 11, return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) .dispatch(Actions.click()) .run() .then(result => expect(result.storeState.clickCount).toBe(14)) }) 
في هذا الاختبار ، لم نعد نتحقق من أنه قد تم تشغيل أي تأثيرات. نتحقق من الحالة النهائية بعد التنفيذ ، وهذا جيد.
لقد نجحنا في التخلص من تطبيق الملحمة ، فلنحاول الآن جعل الاختبار أكثر قابلية للفهم. يكون ذلك سهلاً إذا استبدلت 
then() بـ 
async/await :
 it('should increment click counter (state test with test-plan async-way)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) const result = await saga.dispatch(Actions.click()).run() expect(result.storeState.clickCount).toBe(14) }) 
اختبارات التكامل
ولكن ماذا لو حصلنا أيضًا على عملية النقر العكسي (دعنا نسميها إلغاء النقر) ، والآن يبدو ملف الترهل لدينا كما يلي:
 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* processUnclick() { const result = yield call(ServerApi.SendUnclick) yield put(Actions.clickSuccess(result)) } function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } function* watchUnclick() { yield takeEvery(ActionTypes.UNCLICK, processUnclick) } export default function* mainSaga() { yield all([watchClick(), watchUnclick()]) } 
لنفترض أننا بحاجة إلى اختبار أنه عند استدعاء إجراءات النقر وإلغاء النقر في الحالة ، تتم كتابة نتيجة الرحلة الأخيرة إلى الخادم إلى الحالة. يمكن أيضًا إجراء مثل هذا الاختبار بسهولة باستخدام 
redux-saga-test-plan :
 it('should change click counter (integration test)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(mainSaga) .provide([ call(ServerApi.SendClick), 14], call(ServerApi.SendUnclick), 18] ]) .withReducer(rootReducer, initialState) const result = await saga .dispatch(Actions.click()) .dispatch(Actions.unclick()) .run() expect(result.storeState.clickCount).toBe(18) }) 
يرجى ملاحظة أننا الآن 
mainSaga ، وليس معالجات 
mainSaga الفردية.
ومع ذلك ، إذا أجرينا هذا الاختبار كما هو ، فسوف نحصل على Vorning:

هذا بسبب تأثير 
takeEvery - هذا هو حلقة معالجة الرسائل التي ستعمل بينما تطبيقنا مفتوح. وفقًا لذلك ، فإن الاختبار الذي يُطلق عليه 
takeEvery يكون قادرًا على إكمال العمل دون مساعدة خارجية ، كما أن 
redux-saga-test-plan إعادة 
redux-saga-test-plan تنهي هذه الآثار بالقوة بعد 250 مللي ثانية بعد بدء الاختبار. يمكن تغيير هذه المهلة عن طريق الاتصال expectSaga.DEFAULT_TIMEOUT = 50.
إذا كنت لا ترغب في تلقي مثل هذه vorings ، واحدة لكل اختبار ذات تأثير معقد ، ببساطة استخدم طريقة silentRun() بدلاً من طريقة run() .
المزالق
حيث دون المزالق ... في وقت كتابة هذا التقرير ، أحدث إصدار من redux-saga: 1.0.2. في نفس الوقت ، 
redux-saga-test-plan قادرة على العمل معها فقط على JS.
إذا كنت تريد برنامج TypeScript ، فيجب عليك تثبيت الإصدار من القناة التجريبية:
npm install redux-saga-test-plan@betaوإيقاف الاختبارات من البناء. للقيام بذلك ، في ملف tsconfig.json ، تحتاج إلى تحديد المسار "./src/**/*.spec.ts" في حقل "الاستبعاد".
على الرغم من ذلك ، فإننا نعتبر 
redux-saga-test-plan أفضل مكتبة لاختبار 
redux-saga . إذا كان لديك رد 
redux-saga في مشروعك ، فربما يكون هذا اختيارًا جيدًا لك.
شفرة المصدر للمثال على 
جيثب .