تصف هذه الصفحة أفضل الممارسات والأدوات لكتابة اختبارات الوحدة لوظائفك ، مثل الاختبارات التي ستكون جزءًا من نظام التكامل المستمر (CI). لتسهيل الاختبار ، يوفر Firebase حزمة Firebase Test SDK لوظائف السحابة. يتم توزيعه على npm firebase-functions-test
، وهو اختبار مصاحب لـ SDK firebase-functions
. حزمة تطوير البرامج (SDK) الخاصة باختبار Firebase لوظائف السحابة:
- يعتني بالإعداد والتفكيك المناسب لاختباراتك ، مثل إعداد متغيرات البيئة التي تحتاجها
firebase-functions
وعدم ضبطها. - يولد عينة من البيانات وسياق الحدث ، بحيث يكون عليك فقط تحديد الحقول ذات الصلة بالاختبار الخاص بك.
إعداد الاختبار
قم بتثبيت كل من firebase-functions-test
و Mocha ، إطار اختبار ، عن طريق تشغيل الأوامر التالية في مجلد الوظائف:
npm install --save-dev firebase-functions-test
npm install --save-dev mocha
بعد ذلك ، قم بإنشاء مجلد test
داخل مجلد الوظائف ، وأنشئ ملفًا جديدًا بداخله لرمز الاختبار الخاص بك ، وقم بتسميته شيئًا مثل index.test.js
.
أخيرًا ، قم بتعديل functions/package.json
لإضافة ما يلي:
"scripts": {
"test": "mocha --reporter spec"
}
بمجرد كتابة الاختبارات ، يمكنك تشغيلها عن طريق تشغيل npm test
داخل دليل الوظائف.
تهيئة Firebase Test SDK لوظائف السحابة
توجد طريقتان لاستخدام firebase-functions-test
:
- الوضع عبر الإنترنت (موصى به): اكتب الاختبارات التي تتفاعل مع مشروع Firebase المخصص للاختبار بحيث تحدث فعليًا كتابة قاعدة البيانات وإنشاء المستخدم وما إلى ذلك ، ويمكن لرمز الاختبار الخاص بك فحص النتائج. هذا يعني أيضًا أن مجموعات Google SDK الأخرى المستخدمة في وظائفك ستعمل أيضًا.
- وضع عدم الاتصال: اكتب اختبارات وحدة منفصلة وغير متصلة بدون أي آثار جانبية. هذا يعني أن أي استدعاءات أسلوب تتفاعل مع منتج Firebase (مثل الكتابة إلى قاعدة البيانات أو إنشاء مستخدم) يجب إيقافها. لا يُنصح باستخدام الوضع غير المتصل عمومًا إذا كانت لديك وظائف Cloud Firestore أو Realtime Database ، نظرًا لأنها تزيد بشكل كبير من تعقيد كود الاختبار الخاص بك.
تهيئة SDK في وضع الاتصال (موصى به)
إذا كنت ترغب في كتابة اختبارات تتفاعل مع مشروع اختبار ، فأنت بحاجة إلى توفير قيم تكوين المشروع اللازمة لتهيئة التطبيق من خلال firebase-admin
، والمسار إلى ملف مفتاح حساب الخدمة.
للحصول على قيم تكوين مشروع Firebase:
- افتح إعدادات المشروع في وحدة تحكم Firebase .
- في تطبيقاتك ، حدد التطبيق المطلوب.
في الجزء الأيسر ، حدد خيار تنزيل ملف تكوين لتطبيقات Apple و Android.
بالنسبة لتطبيقات الويب ، حدد تكوين لعرض قيم التكوين.
لإنشاء ملف مفتاح:
- افتح جزء حسابات الخدمة في Google Cloud Console.
- حدد حساب خدمة App Engine الافتراضي ، واستخدم قائمة الخيارات الموجودة على اليمين لتحديد إنشاء مفتاح .
- عند المطالبة ، حدد JSON لنوع المفتاح ، وانقر فوق إنشاء .
بعد حفظ ملف المفتاح ، قم بتهيئة SDK:
// At the top of test/index.test.js
const test = require('firebase-functions-test')({
databaseURL: 'https://my-project.firebaseio.com',
storageBucket: 'my-project.appspot.com',
projectId: 'my-project',
}, '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');
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()
في كود وظائفك ، فأنت بحاجة إلى إيقافه قبل استيراد وظائفك:
// If index.js calls admin.initializeApp at the top of the file, // we need to stub it out before requiring index.js. This is because the // functions will be executed as a part of the require process. // Here we stub admin.initializeApp to be a dummy function that doesn't do anything. adminInitStub = sinon.stub(admin, 'initializeApp'); // 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
يأخذ معلمتين:
- البيانات (مطلوبة): البيانات المراد إرسالها إلى
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
لإنشاء Firestore DocumentSnapshot
. الوسيطة الأولى هي البيانات ، والوسيطة الثانية هي مسار المرجع الكامل ، وهناك وسيطة ثالثة اختيارية للخصائص الأخرى للقطة التي يمكنك تحديدها.
// 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);
راجع مرجع API للحصول على وظائف مماثلة لجميع أنواع البيانات الأخرى.
استخدام بيانات المثال
إذا لم تكن بحاجة إلى تخصيص البيانات المستخدمة في الاختبارات الخاصة بك ، فإن 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();
راجع مرجع API لمعرفة طرق الحصول على بيانات نموذجية لكل نوع دالة.
استخدام بيانات stubbed (للوضع غير المتصل)
إذا قمت بتهيئة 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)
في كود الاختبار ، أنشئ كائنًا عاديًا حيث سيعمل كل من مسارات الكود هذه ، واستخدم Sinon لإيقاف الطرق.
// The following lines creates 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, } } }; childStub.withArgs(childParam).returns({ set: setStub }); setStub.withArgs(setParam).returns(true);
التأكيدات
بعد تهيئة SDK ، وتغليف الوظائف ، وإنشاء البيانات ، يمكنك استدعاء الوظائف المُغلفة بالبيانات المُنشأة وإجراء تأكيدات حول السلوك. يمكنك استخدام مكتبة مثل Chai لعمل هذه التأكيدات.
جعل التأكيدات في وضع الاتصال بالإنترنت
إذا قمت بتهيئة Firebase Test SDK لوظائف السحابة في وضع الاتصال بالإنترنت ، فيمكنك التأكيد على أن الإجراءات المطلوبة (مثل كتابة قاعدة البيانات) قد تم تنفيذها باستخدام حزمة تطوير البرامج (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. Otherwise, add // `admin.initializeApp()` before this line. return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => { // Assert that the value is the uppercased version of our input. assert.equal(createdSnap.val(), 'INPUT'); }); });
جعل التأكيدات في وضع غير متصل بالشبكة
يمكنك عمل تأكيدات حول قيمة الإرجاع المتوقعة للدالة:
const childParam = 'uppercase'; const setParam = 'INPUT'; // Stubs 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 childStub = sinon.stub(); const setStub = sinon.stub(); // The following lines creates 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, } } }; childStub.withArgs(childParam).returns({ set: setStub }); setStub.withArgs(setParam).returns(true); // Wrap the makeUppercase function. const wrapped = test.wrap(myFunctions.makeUppercase); // Since we've stubbed 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 assert.equal(wrapped(snap), true);
يمكنك أيضًا استخدام جواسيس Sinon للتأكيد على أنه تم استدعاء طرق معينة ، ومع المعلمات التي تتوقعها.
اختبار وظائف HTTP
لاختبار وظائف HTTP onCall ، استخدم نفس طريقة اختبار وظائف الخلفية .
إذا كنت تختبر وظائف HTTP onRequest ، فيجب عليك استخدام firebase-functions-test
إذا:
- أنت تستخدم
functions.config()
- تتفاعل وظيفتك مع مشروع Firebase أو واجهات برمجة تطبيقات Google أخرى ، وتريد استخدام مشروع Firebase حقيقي وبيانات اعتماده لاختباراتك.
تأخذ وظيفة HTTP onRequest معلمتين: كائن طلب وكائن استجابة. إليك كيف يمكنك اختبار وظيفة المثال addMessage()
:
- تجاوز وظيفة إعادة التوجيه في كائن الاستجابة ، لأن
sendMessage()
يستدعيها. - ضمن وظيفة إعادة التوجيه ، استخدم chai.assert للمساعدة في عمل تأكيدات حول المعلمات التي يجب استدعاء وظيفة إعادة التوجيه بها:
// 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) => { assert.equal(code, 303); assert.equal(url, '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.
- اختبار وظائف HTTP وقاعدة بيانات Realtime في وضع الاتصال بالإنترنت
- اختبار Realtime Database و HTTP Functions في وضع عدم الاتصال
لمعرفة المزيد ، ارجع إلى مرجع واجهة برمجة التطبيقات (API) الخاص firebase-functions-test
.