בדף הזה מתוארות שיטות מומלצות וכלים לכתיבת בדיקות יחידה לפונקציות, כמו בדיקות שיהיו חלק ממערכת שילוב רציף (CI). כדי להקל על הבדיקות, Firebase מספקת את Firebase Test SDK ל-Cloud Functions. הוא מופץ ב-npm בתור firebase-functions-test
, והוא ערכת SDK לבדיקה שמשלימה את firebase-functions
. הכרטיס Firebase Test SDK של Cloud Functions:
- הוא דואג להגדרה ולביטול ההגדרה המתאימים לבדיקות, כמו הגדרה וביטול הגדרה של משתני סביבה שנדרשים על ידי
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 עבור Cloud Functions
יש שתי דרכים להשתמש ב-firebase-functions-test
:
- מצב אונליין (מומלץ): כותבים בדיקות שיוצרות אינטראקציה עם פרויקט Firebase שמוקדש לבדיקות, כך שפעולות כמו כתיבה למסד הנתונים, יצירת משתמשים וכו' יתבצעו בפועל, וקוד הבדיקה יוכל לבדוק את התוצאות. המשמעות היא שגם ערכות SDK אחרות של Google שמשמשות בפונקציות שלכם יפעלו.
- מצב אופליין: כתיבת בדיקות יחידה מבודדות ואופליין ללא תופעות לוואי. המשמעות היא שצריך ליצור stub לכל קריאות השיטה שמתקשרות עם מוצר של Firebase (לדוגמה, כתיבה למסד הנתונים או יצירת משתמש). בדרך כלל לא מומלץ להשתמש במצב אופליין אם יש לכם פונקציות Cloud Firestore או Realtime Database, כי זה מגדיל מאוד את המורכבות של קוד הבדיקה.
אתחול ה-SDK במצב אונליין (מומלץ)
אם רוצים לכתוב בדיקות שמתקשרות עם פרויקט בדיקה, צריך לספק את ערכי ההגדרה של הפרויקט שנדרשים לאתחול האפליקציה באמצעות firebase-admin
, ואת הנתיב לקובץ מפתח של חשבון שירות.
כדי לקבל את ערכי ההגדרה של פרויקט Firebase:
- פותחים את הגדרות הפרויקט במסוף Firebase.
- בקטע האפליקציות שלך,בוחרים את האפליקציה הרצויה.
בחלונית הימנית, בוחרים באפשרות להורדת קובץ הגדרה לאפליקציות ל-Apple ולאנדרואיד.
באפליקציות אינטרנט, בוחרים באפשרות Config כדי להציג את ערכי ההגדרה.
כדי ליצור קובץ מפתח:
- פותחים את החלונית Service Accounts במסוף Google Cloud.
- בוחרים את App Engine חשבון השירות שמוגדר כברירת מחדל, ובתפריט האפשרויות שמשמאל בוחרים באפשרות Create key.
- כשמופיעה בקשה, בוחרים באפשרות 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()
בקוד של הפונקציות, אפשר ליצור ערכי 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()
בקוד הפונקציות, אתם צריכים ליצור stub לפני ייבוא הפונקציות:
// 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
מקבל 2 פרמטרים:
- data (חובה): הנתונים לשליחה אל
makeUppercase
. הערך הזה תואם ישירות לפרמטר הראשון שנשלח ל-handler של הפונקציה שכתבתם. firebase-functions-test
מספק שיטות ליצירת נתונים מותאמים אישית או נתונים לדוגמה. - eventContextOptions (אופציונלי): שדות של הקשר האירוע שרוצים לציין. הקשר של האירוע הוא הפרמטר השני שנשלח אל פונקציית ה-handler שכתבתם. אם לא כוללים פרמטר
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);
בהפניית ה-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 מפורטות שיטות לקבלת נתונים לדוגמה לכל סוג פונקציה.
שימוש בנתונים של stub (למצב אופליין)
אם הפעלתם את ה-SDK במצב אופליין ואתם בודקים פונקציה Cloud Firestore או Realtime Database, אתם צריכים להשתמש באובייקט פשוט עם stub במקום ליצור 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 כדי ליצור 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);
הוספת הצהרות
אחרי שמפעילים את ה-SDK, עוטפים את הפונקציות ויוצרים נתונים, אפשר להפעיל את הפונקציות העטופות עם הנתונים שנוצרו וליצור טענות לגבי ההתנהגות. אפשר להשתמש בספרייה כמו Chai כדי ליצור את הטענות האלה.
הצהרות במצב אונליין
אם הפעלתם את Firebase Test SDK עבור Cloud Functions במצב אונליין, תוכלו לוודא שהפעולות הרצויות (כמו כתיבה למסד נתונים) התבצעו באמצעות firebase-admin
SDK.
בדוגמה שלמטה, הפונקציה assert בודקת אם הערך 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 wrapped(snap).then(makeUppercaseResult => { return assert.equal(makeUppercaseResult, true); });
אפשר גם להשתמש בSinon spies כדי לוודא שבוצעו קריאות ל-methods מסוימים, ועם הפרמטרים שציפיתם להם.
בדיקת פונקציות HTTP
כדי לבדוק פונקציות HTTP onCall, משתמשים באותה גישה כמו בבדיקת פונקציות ברקע.
אם אתם בודקים פונקציות HTTP onRequest, אתם צריכים להשתמש ב-firebase-functions-test
אם:
- אתם משתמשים ב-
functions.config()
- הפונקציה שלכם מקיימת אינטראקציה עם פרויקט Firebase או עם Google APIs אחרים, ואתם רוצים להשתמש בפרויקט 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.
- בדיקה של פונקציות Realtime Database ו-HTTP במצב אונליין
- בדיקה של Realtime Database ושל פונקציות HTTP במצב אופליין
מידע נוסף זמין במאמר בנושא הפניית API של firebase-functions-test
.