本頁說明為
功能,例如即將納入持續整合 (CI) 的測試
有些人會將 Cloud Storage 視為檔案系統
但實際上不是為簡化測試作業,Firebase 提供 Cloud Functions 的 Firebase Test SDK。這項服務
在 npm 上以 firebase-functions-test
的形式發布,為隨附測試 SDK
至 firebase-functions
。Cloud Functions 的 Firebase Test SDK:
- 為測試進行適當的設定和拆解工作,例如
設定及取消
firebase-functions
所需的環境變數。 - 產生範例資料和事件情境,因此只需要指定 與測試相關的欄位。
測試設定
請同時安裝 firebase-functions-test
和 Mocha (
測試架構,方法是在 functions 資料夾中執行下列指令:
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
以執行測試
您的函式目錄。
正在初始化「Cloud Functions」的「Firebase Test SDK」
firebase-functions-test
有兩種使用方法:
- 線上模式 (建議選項):撰寫與 Firebase 專案互動的測試 主要用於測試,這可讓資料庫寫入、使用者建立等作業 測試程式碼來檢查結果。這也表示 函式中使用的 Google SDK 也可正常運作。
- 離線模式:撰寫孤立或離線單元測試,而不使用副作用。 這代表與 Firebase 產品互動的任何方法呼叫 (例如 寫入資料庫或建立使用者) 均須加上虛設常式。離線使用 如果您已擁有 Cloud Firestore 或 Realtime Database,則通常不建議使用模式 函式,因為會大幅增加測試程式碼的複雜度。
在線上模式中初始化 SDK (建議做法)
如要編寫與測試專案互動的測試,您需要
提供初始化應用程式所需的專案設定值
firebase-admin
,以及服務帳戶金鑰檔案的路徑。
如要取得 Firebase 專案的設定值,請按照下列步驟操作:
- 在 Firebase 控制台。
- 在「你的應用程式」中,選取所需應用程式。
在右側窗格中,選取下載設定檔的選項 適用於 Apple 和 Android 應用程式
如果是網頁應用程式,選取「Config」就會顯示 設定值
如要建立金鑰檔案,請按照下列步驟操作:
- 開啟「服務帳戶」窗格 Google Cloud控制台中就能保留這項資訊
- 選取 App Engine 預設服務帳戶,然後使用下列選項選單: 讓您選取「建立金鑰」。
- 系統提示時,請選取 JSON 做為金鑰類型,然後按一下「Create」(建立)。
儲存金鑰檔案後,請初始化 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/v1');
const key = functions.config().stripe.key;
接著,您可以在測試檔案中模擬值,如下所示:
// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});
匯入函式
如要匯入函式,請使用 require
匯入主要函式檔案,做為
後續課程我們將逐一介紹
預先訓練的 API、AutoML 和自訂訓練請務必僅在初始化 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
方法納入要測試的函式 - 建立測試資料
- 使用您建立的測試資料與任何事件叫用已包裝的函式 選取要指定的結構定義欄位
- 斷言行為。
請先包裝要測試的函式。假設您在
名為 makeUppercase
的 functions/index.js
,您需要進行測試。撰寫
在「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
的資料。這直接 對應至傳送至函式處理常式的第一個參數 寫作。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 參考資料。 取得每種函式類型的範例資料
使用虛設常式資料 (適用於離線模式)
如果您是在離線模式下初始化 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 等程式庫 做出這些斷言
以線上模式做出斷言
如果您已在線上模式中初始化 Cloud Functions 的 Firebase Test SDK,
並斷言已對物件執行所需動作 (例如資料庫寫入)
firebase-admin
SDK
以下範例斷言「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);
你還能使用日本間諜 使用預期的參數呼叫特定方法。
測試 HTTP 函式
如要測試 HTTP onCall 函式,請使用與測試背景函式相同的方法。
如要測試 HTTP onRequest 函式,則應使用
如果符合下列條件,則為firebase-functions-test
:
- 您使用「
functions.config()
」 - 您的函式會與 Firebase 專案或其他 Google API 互動,且 您想使用實際的 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 在初始化時設定的環境變數,然後刪除
如果您使用 SDK 建立
時間資料庫 DataSnapshot
或 Firestore DocumentSnapshot
。
test.cleanup();
查看完整範例並瞭解詳情
您可以在 Firebase GitHub 存放區中查看完整範例。
詳情請參閱 API 參考資料
(firebase-functions-test
)