يوفر إطار عمل
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
في مشروعك ، فربما يكون هذا اختيارًا جيدًا لك.
شفرة المصدر للمثال على
جيثب .