توضّح هذه الصفحة أفضل الممارسات والأدوات لكتابة اختبارات الوحدات للدوال، مثل الاختبارات التي ستكون جزءًا من نظام التكامل المستمر (CI). لتسهيل عملية الاختبار، يوفّر Firebase Firebase Test SDK لـ Cloud Functions. ويتم توزيعها على npm باسم firebase-functions-test، وهي حزمة SDK مصاحبة للاختبار مع firebase-functions. Firebase Test SDK لـ Cloud Functions:
- يتولّى هذا الإجراء الإعداد المناسب للاختبارات وإلغاء الإعدادات، مثل ضبط المتغيّرات البيئية التي يحتاجها "
firebase-functions" وإلغاء ضبطها. - تنشئ عيّنات بيانات وسياق الأحداث، بحيث لا تحتاج إلا إلى تحديد الحقول ذات الصلة بالاختبار.
إعداد الاختبار
ثبِّت كلاً من firebase-functions-test وJest، وهو إطار عمل للاختبار، من خلال تنفيذ الأوامر التالية في مجلد الدوال:
npm install --save-dev firebase-functions-test
npm install --save-dev jest
بعد ذلك، أنشِئ مجلد test داخل مجلد الدوال، وأنشِئ ملفًا جديدًا
بداخله لرمز الاختبار، واختَر له اسمًا مثل index.test.js.
أخيرًا، عدِّل functions/package.json لإضافة ما يلي:
"scripts": {
"test": "jest"
}
بعد كتابة الاختبارات، يمكنك تشغيلها من خلال تشغيل npm test داخل
دليل الدوال.
جارٍ إعداد Firebase Test SDK لـ Cloud Functions
هناك طريقتان لاستخدام firebase-functions-test:
- وضع الاتصال بالإنترنت (يُنصح به): يمكنك كتابة اختبارات تتفاعل مع مشروع Firebase مخصّص للاختبار بحيث تحدث عمليات كتابة قاعدة البيانات وإنشاء المستخدمين وما إلى ذلك فعليًا، ويمكن لرمز الاختبار فحص النتائج. وهذا يعني أيضًا أنّ حِزم تطوير البرامج (SDK) الأخرى من Google المستخدَمة في الدوال ستعمل أيضًا.
- وضع عدم الاتصال بالإنترنت: يمكنك كتابة اختبارات وحدة معزولة وبلا إنترنت بدون أي آثار جانبية. وهذا يعني أنّه يجب إنشاء كود بديل لأي طلبات إجراء تتفاعل مع منتج Firebase (مثل الكتابة في قاعدة البيانات أو إنشاء مستخدم). لا يُنصح عمومًا باستخدام وضع عدم الاتصال بالإنترنت إذا كانت لديك دوال Cloud Firestore أو Realtime Database، لأنّ ذلك يزيد بشكل كبير من تعقيد رمز الاختبار.
إعداد حزمة تطوير البرامج (SDK) في وضع الاتصال بالإنترنت (يُنصح به)
إذا أردت كتابة اختبارات تتفاعل مع مشروع تجريبي، عليك تقديم قيم إعدادات Firebase اللازمة لإعداد التطبيق من خلال firebase-admin، ومسار ملف مفتاح حساب الخدمة.
للحصول على قيم إعدادات Firebase:
في وحدة تحكّم Firebase، انتقِل إلى
الإعدادات > صفحة "الإعدادات العامة".انتقِل إلى بطاقة تطبيقاتك، واختَر التطبيق المطلوب.
احصل على إعدادات Firebase:
بالنسبة إلى تطبيقات Apple وAndroid، حدِّد خيار تنزيل ملف الإعداد.
بالنسبة إلى تطبيقات الويب، اختَر الإعدادات لعرض قيم الإعدادات.
لإنشاء ملف مفتاح:
في وحدة تحكّم Google Cloud، انتقِل إلى لوحة حسابات الخدمة.
اختَر حساب الخدمة التلقائي "App Engine"، واستخدِم قائمة الخيارات على اليسار لاختيار إنشاء مفتاح.
عندما يُطلب منك ذلك، اختَر JSON لنوع المفتاح، ثم انقر على إنشاء.
بعد حفظ ملف المفتاح، عليك إعداد حزمة SDK:
// At the top of test/index.test.js
// Make sure to use values from your actual Firebase configuration
const test = require('firebase-functions-test')({
databaseURL: 'https://PROJECT_ID.firebaseio.com',
storageBucket: 'PROJECT_ID.firebasestorage.app ',
projectId: 'PROJECT_ID',
}, 'path/to/serviceAccountKey.json');
إعداد حزمة تطوير البرامج (SDK) في وضع عدم الاتصال بالإنترنت
إذا أردت كتابة اختبارات بلا إنترنت بالكامل، يمكنك إعداد حزمة تطوير البرامج (SDK) بدون أي مَعلمات:
// At the top of test/index.test.js
const test = require('firebase-functions-test')();
محاكاة قيم الإعدادات
إذا كنت تستخدم functions.config() في رمز الدوال، يمكنك محاكاة قيم
الإعدادات. على سبيل المثال، إذا كان functions/index.js يحتوي على الرمز التالي:
const functions = require('firebase-functions/v1');
const key = functions.config().stripe.key;
بعد ذلك، يمكنك محاكاة القيمة داخل ملف الاختبار على النحو التالي:
// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});
جارٍ استيراد الدوال
لاستيراد الدوال، استخدِم require لاستيراد ملف الدوال الرئيسي كوحدة. يجب التأكّد من تنفيذ ذلك فقط بعد إعداد firebase-functions-test
ومحاكاة قيم الإعدادات.
// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code
إذا تم إعداد firebase-functions-test في
وضع عدم الاتصال بالإنترنت، وكان لديك
admin.initializeApp() في رمز الدوال، عليك إنشاء رمز بديل له قبل
استيراد الدوال. في ملف الاختبار، اطلب firebase-admin كـ
admin، ثم استبدِل initializeApp() بمحاكاة Jest:
// If initializeApp() is called in index.js, we mock it out before requiring index.js adminInitStub = jest.spyOn(admin, 'initializeApp').mockImplementation(() => {}); // Now we can require index.js and save the exports inside a namespace called myFunctions. myFunctions = require('../index');
اختبار الدوال التي تعمل في الخلفية (غير المستندة إلى بروتوكول HTTP)
تتضمّن عملية اختبار الدوال غير المستندة إلى HTTP الخطوات التالية:
- أضِف الدالة التي تريد اختبارها باستخدام طريقة
test.wrap - إنشاء بيانات الاختبار
- استدعِ الدالة المغلَّفة باستخدام بيانات الاختبار التي أنشأتها وأي حقول سياق أحداث تريد تحديدها.
- قدِّم تأكيدات بشأن السلوك.
أولاً، عليك تضمين الدالة التي تريد اختبارها. لنفترض أنّ لديك دالة في functions/index.js باسم makeUppercase، وتريد اختبارها. اكتب ما يلي باللغة functions/test/index.test.js
// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);
wrapped هي دالة تستدعي makeUppercase عند استدعائها. تتطلّب الدالة wrapped مَعلمتَين:
- data (مطلوبة): البيانات المطلوب إرسالها إلى
makeUppercase. يتوافق هذا مباشرةً مع المَعلمة الأولى التي تم إرسالها إلى معالج الدوال الذي كتبته. توفّرfirebase-functions-testطرقًا لإنشاء بيانات مخصّصة أو بيانات نموذجية. - eventContextOptions (اختياري): حقول سياق الحدث التي تريد تحديدها سياق الحدث هو المعلمة الثانية المرسلة إلى
معالج الدالة الذي كتبته. في حال عدم تضمين مَعلمة
eventContextOptionsعند طلبwrapped، سيتم إنشاء سياق حدث باستخدام حقول معقولة. يمكنك إلغاء بعض الحقول التي تم إنشاؤها من خلال تحديدها هنا. يُرجى العِلم أنّه عليك فقط تضمين الحقول التي تريد إلغاءها. يتم إنشاء أي حقول لم تلغِها.
const data = … // See next section for constructing test data
// Invoke the wrapped function without specifying the event context.
wrapped(data);
// Invoke the function, and specify params
wrapped(data, {
params: {
pushId: '234234'
}
});
// Invoke the function, and specify auth and auth Type (for real time database functions only)
wrapped(data, {
auth: {
uid: 'jckS2Q0'
},
authType: 'USER'
});
// Invoke the function, and specify all the fields that can be specified
wrapped(data, {
eventId: 'abc',
timestamp: '2018-03-23T17:27:17.099Z',
params: {
pushId: '234234'
},
auth: {
uid: 'jckS2Q0' // only for real time database functions
},
authType: 'USER' // only for real time database functions
});
إنشاء بيانات الاختبار
المَعلمة الأولى للدالة المغلَّفة هي بيانات الاختبار التي يتم استخدامها لاستدعاء الدالة الأساسية. هناك عدد من الطرق لإنشاء بيانات الاختبار.
استخدام البيانات المخصّصة
تحتوي firebase-functions-test على عدد من الدوال لإنشاء البيانات اللازمة
لاختبار الدوال. على سبيل المثال، يمكنك استخدام test.firestore.makeDocumentSnapshot
لإنشاء DocumentSnapshot في Firestore. الوسيطة الأولى هي البيانات، والوسيطة الثانية هي مسار المرجع الكامل، وهناك
وسيطة ثالثة اختيارية
لسمات أخرى من اللقطة يمكنك تحديدها.
// Make snapshot
const snap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Call wrapped function with the snapshot
const wrapped = test.wrap(myFunctions.myFirestoreDeleteFunction);
wrapped(snap);
إذا كنت تختبر دالة onUpdate أو onWrite، فستحتاج إلى إنشاء لقطتين: واحدة للحالة السابقة والأخرى للحالة اللاحقة. بعد ذلك، يمكنك استخدام طريقة makeChange لإنشاء عنصر Change باستخدام هذه اللقطات.
// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot({foo: 'faz'}, 'document/path');
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(myFunctions.myFirestoreUpdateFunction);
wrapped(change);
يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات للتعرّف على دوال مشابهة لجميع أنواع البيانات الأخرى.
استخدام بيانات نموذجية
إذا لم تكن بحاجة إلى تخصيص البيانات المستخدَمة في الاختبارات، يوفّر firebase-functions-test طرقًا لإنشاء بيانات نموذجية لكل نوع دالة.
// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();
يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات للتعرّف على طرق الحصول على بيانات أمثلة لكل نوع دالة.
استخدام البيانات الوهمية (لوضع عدم الاتصال بالإنترنت)
إذا تم إعداد حزمة تطوير البرامج (SDK) في وضع عدم الاتصال بالإنترنت، وكنت تختبر دالة Cloud Firestore أو Realtime Database، عليك استخدام عنصر عادي مع عناصر نائبة بدلاً من إنشاء DocumentSnapshot أو DataSnapshot فعلي.
لنفترض أنّك تكتب اختبار وحدة للدالة التالية:
// Listens for new messages added to /messages/:pushId/original and creates an // uppercase version of the message to /messages/:pushId/uppercase exports.makeUppercase = functions.database.ref('/messages/{pushId}/original') .onCreate((snapshot, context) => { // Grab the current value of what was written to the Realtime Database. const original = snapshot.val(); functions.logger.log('Uppercasing', context.params.pushId, original); const uppercase = original.toUpperCase(); // You must return a Promise when performing asynchronous tasks inside a Functions such as // writing to the Firebase Realtime Database. // Setting an "uppercase" sibling in the Realtime Database returns a Promise. return snapshot.ref.parent.child('uppercase').set(uppercase); });
داخل الدالة، يتم استخدام snap مرتين:
snap.val()snap.ref.parent.child('uppercase').set(uppercase)
في الرمز البرمجي للاختبار، أنشئ كائنًا عاديًا يعمل فيه كلا المسارَين البرمجيَّين، واستخدِم الدوال الوهمية في Jest لإنشاء دوال بديلة للطرق.
// The following lines create a fake snapshot, 'snap', which returns 'input' when snap.val() is called, // and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called. const snap = { val: () => 'input', ref: { parent: { child: childStub, } } };
تقديم تأكيدات
بعد إعداد حزمة تطوير البرامج (SDK) وتضمين الدوال وإنشاء البيانات، يمكنك
استدعاء الدوال المضمّنة مع البيانات التي تم إنشاؤها وإجراء تأكيدات
حول السلوك. يمكنك استخدام expect في Jest
لإجراء هذه التأكيدات.
تقديم تأكيدات في وضع الاتصال بالإنترنت
إذا تم إعداد Firebase Test SDK لـ "Cloud Functions" في وضع الاتصال بالإنترنت، يمكنك
التأكّد من تنفيذ الإجراءات المطلوبة (مثل كتابة قاعدة بيانات) باستخدام
حزمة تطوير البرامج (SDK) لـ "firebase-admin".
يؤكّد المثال أدناه أنّه تمت كتابة "INPUT" في قاعدة بيانات مشروع الاختبار.
// Create a DataSnapshot with the value 'input' and the reference path 'messages/11111/original'. const snap = test.database.makeDataSnapshot('input', 'messages/11111/original'); // Wrap the makeUppercase function const wrapped = test.wrap(myFunctions.makeUppercase); // Call the wrapped function with the snapshot you constructed. return wrapped(snap).then(() => { // Read the value of the data at messages/11111/uppercase. Because `admin.initializeApp()` is // called in functions/index.js, there's already a Firebase app initialized. return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => { // Assert that the value is the uppercased version of our input. expect(createdSnap.val()).toBe('INPUT'); }); });
تقديم التأكيدات في وضع عدم الاتصال بالإنترنت
يمكنك تقديم تأكيدات حول القيمة المعروضة المتوقّعة للدالة:
const childParam = 'uppercase'; const setParam = 'INPUT'; // Spies/mocks are objects that fake and/or record function calls. // These are excellent for verifying that functions have been called and to validate the // parameters passed to those functions. const setStub = jest.fn().mockImplementation((val) => val === setParam ? true : undefined); const childStub = jest.fn().mockImplementation((path) => path === childParam ? { set: setStub } : undefined); // The following lines create a fake snapshot, 'snap', which returns 'input' when snap.val() is called, // and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called. const snap = { val: () => 'input', ref: { parent: { child: childStub, } } }; // Wrap the makeUppercase function. const wrapped = test.wrap(myFunctions.makeUppercase); // Since we've mocked snap.ref.parent.child(childParam).set(setParam) to return true if it was // called with the parameters we expect, we assert that it indeed returned true. return wrapped(snap).then(makeUppercaseResult => { expect(makeUppercaseResult).toBe(true); });
يمكنك أيضًا استخدام دوال Jest الوهمية للتأكد من استدعاء طرق معينة، ومع المعلمات التي تتوقعها.
اختبار دوال HTTP
لاختبار دوال onCall التي تستخدم بروتوكول HTTP، اتّبِع الطريقة نفسها المتّبَعة في اختبار دوال الخلفية.
إذا كنت تختبر دوال onRequest لبروتوكول HTTP، عليك استخدام
firebase-functions-test في الحالات التالية:
- أنت تستخدم
functions.config() - تتفاعل الدالة مع مشروع Firebase أو واجهات برمجة تطبيقات Google الأخرى، وتريد استخدام مشروع Firebase حقيقي وبيانات اعتماده في الاختبارات.
تتلقّى دالة onRequest لبروتوكول HTTP مَعلمتَين: عنصر طلب وعنصر استجابة. في ما يلي كيفية اختبار دالة addMessage() كمثال:
يجب إنشاء رمز بديل لـ "
admin.database()" لأنّ "addMessage()" يرسل البيانات إلى قاعدة بيانات الوقت الفعلي. في وضع عدم الاتصال بالإنترنت، تم استبدالinitializeApp()، لذا لا يتوفّر تطبيق Firebase لاستخدامه من قِبل مدير SDK، ويتعذّر تنفيذadmin.database()الحقيقي بسببThe default Firebase app does not exist:const pushStub = jest.fn().mockResolvedValue({ ref: 'new_ref' }); const refStub = jest.fn().mockReturnValue({ push: pushStub }); Object.defineProperty(admin, 'database', { configurable: true, writable: true, value: jest.fn().mockReturnValue({ ref: refStub }), });تجاوز دالة إعادة التوجيه في كائن الاستجابة، لأنّ
addMessage()يستدعيها.ضمن دالة إعادة التوجيه، استخدِم
expectمن Jest لإجراء تأكيدات حول المَعلمات التي يجب استدعاء دالة إعادة التوجيه بها:
// A fake request object, with req.query.text set to 'input' const req = { query: {text: 'input'} }; // A fake response object, with a stubbed redirect function which asserts that it is called // with parameters 303, 'new_ref'. const res = { redirect: (code, url) => { expect(code).toBe(303); expect(url).toBe('new_ref'); done(); } }; // Invoke addMessage with our fake request and response objects. This will cause the // assertions in the response object to be evaluated. myFunctions.addMessage(req, res);
تنظيف الاختبار
في نهاية رمز الاختبار، استدعِ دالة التنظيف. يؤدي ذلك إلى إلغاء ضبط متغيرات البيئة التي ضبطتها حزمة تطوير البرامج (SDK) عند إعدادها، وحذف تطبيقات Firebase التي قد تكون تم إنشاؤها إذا استخدمت حزمة تطوير البرامج (SDK) لإنشاء قاعدة بيانات في الوقت الفعلي DataSnapshot أو Firestore DocumentSnapshot.
test.cleanup();
مراجعة أمثلة كاملة والاطّلاع على مزيد من المعلومات
يمكنك مراجعة الأمثلة الكاملة في مستودع Firebase على GitHub.
- اختبار دوال Realtime Database وHTTP في وضع الاتصال بالإنترنت
- اختبار دوال Realtime Database وHTTP في وضع عدم الاتصال بالإنترنت
لمزيد من المعلومات، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقات
لـ firebase-functions-test.