หน้านี้อธิบายแนวทางปฏิบัติแนะนำและเครื่องมือสำหรับการเขียน Unit Test สำหรับฟังก์ชัน เช่น การทดสอบที่จะเป็นส่วนหนึ่งของระบบการผสานรวมอย่างต่อเนื่อง (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 ได้ 2 วิธี ดังนี้
- โหมดออนไลน์ (แนะนํา): เขียนการทดสอบที่โต้ตอบกับโปรเจ็กต์ 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() ในโค้ดฟังก์ชัน คุณจะต้องจำลองก่อน
นำเข้าฟังก์ชัน
// 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
ใช้พารามิเตอร์ 2 รายการ ได้แก่
- data (ต้องระบุ): ข้อมูลที่จะส่งไปยัง
makeUppercaseซึ่งสอดคล้องกับพารามิเตอร์แรกที่ส่งไปยังตัวแฮนเดิลฟังก์ชันที่คุณเขียนโดยตรงfirebase-functions-testมีวิธีการสร้างข้อมูลที่กำหนดเอง หรือข้อมูลตัวอย่าง - eventContextOptions (ไม่บังคับ): ฟิลด์ของบริบทเหตุการณ์ที่คุณต้องการระบุ
บริบทเหตุการณ์คือพารามิเตอร์ที่ 2 ที่ส่งไปยัง
ตัวแฮนเดิลฟังก์ชันที่คุณเขียน หากคุณไม่ใส่
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 คุณจะต้องสร้าง
สแนปชอต 2 รายการ ได้แก่ สแนปชอตสำหรับสถานะก่อนหน้าและสแนปชอตสำหรับสถานะหลังจากนั้น จากนั้นคุณ
จะใช้วิธี 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 คุณควรใช้ออบเจ็กต์ธรรมดาที่มี Stub
แทนที่จะสร้าง DocumentSnapshot หรือ DataSnapshot จริง
สมมติว่าคุณกำลังเขียน Unit Test สำหรับฟังก์ชันต่อไปนี้
// Listens for new messages added to /messages/:documentId/original and creates an // uppercase version of the message to /messages/:documentId/uppercase exports.makeUppercase = functions.firestore .document("/messages/{documentId}") .onCreate((snap, context) => { // Grab the current value of what was written to Firestore. const original = snap.data().original; // Access the parameter `{documentId}` with `context.params` functions.logger.log("Uppercasing", context.params.documentId, original); const uppercase = original.toUpperCase(); // You must return a Promise when performing asynchronous tasks inside a Functions such as // writing to Firestore. // Setting an 'uppercase' field in Firestore document returns a Promise. return snap.ref.set({ uppercase }, { merge: true }); });
ภายในฟังก์ชัน จะใช้ snap 2 ครั้ง ดังนี้
snap.val()snap.ref.parent.child('uppercase').set(uppercase)
ในโค้ดทดสอบ ให้สร้างออบเจ็กต์ธรรมดาที่เส้นทางโค้ดทั้ง 2 เส้นทางนี้จะทำงานได้ และใช้ Sinon เพื่อจำลองเมธอด
// 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, ห่อหุ้มฟังก์ชัน และสร้างข้อมูลแล้ว คุณจะเรียกใช้ฟังก์ชันที่ห่อหุ้มด้วยข้อมูลที่สร้างขึ้นและทำการยืนยันเกี่ยวกับลักษณะการทำงานได้ คุณสามารถใช้ไลบรารี เช่น Chai เพื่อ ทำการยืนยันเหล่านี้
การยืนยันในโหมดออนไลน์
หากคุณเริ่มต้น 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); });
นอกจากนี้ คุณยังใช้ Sinon spies เพื่อ ยืนยันว่ามีการเรียกใช้เมธอดบางอย่าง และมีพารามิเตอร์ที่คุณคาดหวังได้ด้วย
การทดสอบฟังก์ชัน HTTP
หากต้องการทดสอบฟังก์ชัน HTTP onCall ให้ใช้วิธีเดียวกับการทดสอบฟังก์ชันที่ทำงานเบื้องหลัง
หากคุณกำลังทดสอบฟังก์ชัน HTTP onRequest คุณควรใช้
firebase-functions-test ในกรณีต่อไปนี้
- คุณใช้
functions.config() - ฟังก์ชันของคุณโต้ตอบกับโปรเจ็กต์ Firebase หรือ Google APIs อื่นๆ และ คุณต้องการใช้โปรเจ็กต์ Firebase จริงและข้อมูลเข้าสู่ระบบของโปรเจ็กต์นั้นสำหรับการทดสอบ
ฟังก์ชัน HTTP onRequest ใช้พารามิเตอร์ 2 รายการ ได้แก่ ออบเจ็กต์คำขอและออบเจ็กต์การตอบกลับ
คุณอาจทดสอบ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) => { 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 ในโหมดออฟไลน์
ดูข้อมูลเพิ่มเติมได้ในเอกสารอ้างอิง API
สำหรับ firebase-functions-test