หน้านี้อธิบายแนวทางปฏิบัติแนะนำและเครื่องมือสำหรับการเขียนการทดสอบหน่วยสำหรับฟังก์ชัน เช่น การทดสอบที่จะเป็นส่วนหนึ่งของระบบการผสานรวมอย่างต่อเนื่อง (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-admin
และเส้นทางไปยังไฟล์คีย์บัญชีบริการ
วิธีดูค่าการกำหนดค่าของโปรเจ็กต์ Firebase
- เปิดการตั้งค่าโปรเจ็กต์ในFirebase Console
- เลือกแอปที่ต้องการในแอปของคุณ
ในแผงด้านขวา ให้เลือกตัวเลือกเพื่อดาวน์โหลดไฟล์การกำหนดค่า สำหรับแอป 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 ที่ไม่บังคับ
สำหรับพร็อพเพอร์ตี้อื่นๆ ของสแนปชอตที่คุณระบุได้
// 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
ด้วยสแนปชอตเหล่านี้
// 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
จริง
สมมติว่าคุณกำลังเขียนการทดสอบหน่วยสำหรับฟังก์ชันต่อไปนี้
// 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, Wrapper ฟังก์ชัน และสร้างข้อมูลแล้ว คุณ จะเรียกใช้ฟังก์ชันที่ Wrapper ด้วยข้อมูลที่สร้างขึ้นและทำการยืนยัน เกี่ยวกับลักษณะการทำงานได้ คุณสามารถใช้ไลบรารี เช่น 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 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) => { 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