หน้านี้อธิบายแนวทางปฏิบัติแนะนำและเครื่องมือสำหรับการเขียน Unit Test สำหรับฟังก์ชัน เช่น การทดสอบที่จะเป็นส่วนหนึ่งของระบบการผสานรวมอย่างต่อเนื่อง (CI) Firebase มี Firebase Test SDK สำหรับ Cloud Functions เพื่อให้การทดสอบง่ายขึ้น โดย SDK นี้เผยแพร่ใน 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 ที่ใช้ในฟังก์ชันก็จะทำงานด้วยเช่นกัน
- โหมดออฟไลน์: เขียน Unit Test แบบแยกส่วนและออฟไลน์โดยไม่มีผลข้างเคียง ซึ่งหมายความว่าการเรียกใช้เมธอดใดก็ตามที่โต้ตอบกับผลิตภัณฑ์ Firebase (เช่น การเขียนลงในฐานข้อมูลหรือการสร้างผู้ใช้) จะต้องมีการจำลอง โดยทั่วไปเราไม่แนะนำให้ใช้โหมดออฟไลน์ หากคุณมี Cloud Firestore หรือ Realtime Database เนื่องจากจะเพิ่มความซับซ้อนของโค้ดทดสอบอย่างมาก
เริ่มต้น SDK ในโหมดออนไลน์ (แนะนำ)
หากต้องการเขียนการทดสอบที่โต้ตอบกับโปรเจ็กต์ทดสอบ คุณต้องระบุค่าการกำหนดค่า Firebase ที่จำเป็นสำหรับการเริ่มต้นแอปผ่าน firebase-admin และเส้นทางไปยังไฟล์คีย์บัญชีบริการ
วิธีรับค่าการกำหนดค่า Firebase
ในคอนโซล Firebase ให้ไปที่หน้า
Settings > Generalเลื่อนไปที่การ์ดแอปของคุณ แล้วเลือกแอปที่ต้องการ
รับการกำหนดค่า 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 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ซึ่งสอดคล้องกับพารามิเตอร์แรกที่ส่งไปยังตัวจัดการฟังก์ชันที่คุณเขียน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 อาร์กิวเมนต์แรกคือข้อมูล และ
อาร์กิวเมนต์ที่ 2 คือเส้นทางการอ้างอิงแบบเต็ม และมี
อาร์กิวเมนต์ที่ 3 ที่ไม่บังคับ
สำหรับพร็อพเพอร์ตี้อื่นๆ ของ Snapshot ที่คุณระบุได้
// 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 คุณจะต้องสร้าง Snapshot 2 รายการ ได้แก่ Snapshot สำหรับสถานะก่อนหน้าและ Snapshot สำหรับสถานะหลัง จากนั้นคุณ
สามารถใช้เมธอด makeChange เพื่อสร้างออบเจ็กต์ Change ที่มี Snapshot เหล่านี้
// 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 จริง
สมมติว่าคุณกำลังเขียน Unit Test สำหรับฟังก์ชันต่อไปนี้
// 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 2 ครั้งดังนี้
snap.val()snap.ref.parent.child('uppercase').set(uppercase)
ในโค้ดทดสอบ ให้สร้างออบเจ็กต์ธรรมดาที่เส้นทางโค้ดทั้ง 2 เส้นทางนี้จะทำงานได้ และใช้ 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 สำหรับ Cloud Functions ใน โหมดออนไลน์ คุณ
สามารถยืนยันว่ามีการดำเนินการที่ต้องการ (เช่น การเขียนฐานข้อมูล) โดย
ใช้ 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 wrapped(snap).then(makeUppercaseResult => { return assert.equal(makeUppercaseResult, true); });
นอกจากนี้ คุณยังใช้ Sinon Spies เพื่อ ยืนยันว่ามีการเรียกใช้เมธอดบางอย่างและมีการใช้พารามิเตอร์ที่คุณคาดไว้
การทดสอบฟังก์ชัน HTTP
หากต้องการทดสอบฟังก์ชัน HTTP onCall ให้ใช้วิธีเดียวกับการทดสอบฟังก์ชันพื้นหลัง
หากคุณกำลังทดสอบฟังก์ชัน HTTP onRequest คุณควรใช้ firebase-functions-test ในกรณีต่อไปนี้
- คุณใช้
functions.config() - ฟังก์ชันของคุณโต้ตอบกับโปรเจ็กต์ Firebase หรือ Google API อื่นๆ และคุณต้องการใช้โปรเจ็กต์ 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) => { 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 ของ Realtime Database หรือ DocumentSnapshot ของ Firestore
test.cleanup();
ดูตัวอย่างที่สมบูรณ์และดูข้อมูลเพิ่มเติม
คุณสามารถดูตัวอย่างที่สมบูรณ์ได้ในที่เก็บ Firebase GitHub
- การทดสอบ Realtime Database และฟังก์ชัน HTTP ในโหมดออนไลน์
- การทดสอบ Realtime Database และฟังก์ชัน HTTP ในโหมดออฟไลน์
ดูข้อมูลเพิ่มเติมได้ที่ เอกสารอ้างอิง API
สำหรับ firebase-functions-test