หน้านี้อธิบายแนวทางปฏิบัติที่ดีที่สุดและเครื่องมือสำหรับการเขียนการทดสอบหน่วยสำหรับฟังก์ชันของคุณ เช่น การทดสอบที่จะเป็นส่วนหนึ่งของระบบบูรณาการต่อเนื่อง (CI) เพื่อให้การทดสอบง่ายขึ้น Firebase มี Firebase Test SDK สำหรับ Cloud Functions มีการแจกจ่ายใน npm เป็น firebase-functions-test
และเป็น SDK ทดสอบร่วมกับ firebase-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
ภายในไดเร็กทอรีฟังก์ชันของคุณ
กำลังเริ่มต้น Firebase Test SDK สำหรับฟังก์ชันคลาวด์
มีสองวิธีในการใช้ firebase-functions-test
:
- โหมดออนไลน์ (แนะนำ): การทดสอบการเขียนที่โต้ตอบกับโปรเจ็กต์ Firebase สำหรับการทดสอบโดยเฉพาะ เพื่อให้การเขียนฐานข้อมูล ผู้ใช้สร้าง และอื่นๆ เกิดขึ้นจริง และโค้ดทดสอบของคุณสามารถตรวจสอบผลลัพธ์ได้ นอกจากนี้ยังหมายความว่า Google SDK อื่นๆ ที่ใช้ในฟังก์ชันของคุณจะใช้งานได้เช่นกัน
- โหมดออฟไลน์: เขียนการทดสอบหน่วยแบบแยกและออฟไลน์โดยไม่มีผลข้างเคียง ซึ่งหมายความว่าการเรียกเมธอดใดๆ ที่โต้ตอบกับผลิตภัณฑ์ Firebase (เช่น การเขียนไปยังฐานข้อมูลหรือการสร้างผู้ใช้) จะต้องถูกระงับ โดยทั่วไปไม่แนะนำให้ใช้โหมดออฟไลน์หากคุณมีฟังก์ชัน Cloud Firestore หรือฐานข้อมูลเรียลไทม์ เนื่องจากจะเพิ่มความซับซ้อนของโค้ดทดสอบอย่างมาก
เริ่มต้น SDK ในโหมดออนไลน์ (แนะนำ)
หากคุณต้องการเขียนการทดสอบที่โต้ตอบกับโปรเจ็กต์ทดสอบ คุณต้องระบุค่าคอนฟิกโปรเจ็กต์ที่จำเป็นสำหรับการเริ่มต้นแอปผ่าน firebase-admin
และพาธไปยังไฟล์คีย์ของบัญชีบริการ
วิธีรับค่ากำหนดของโครงการ Firebase:
- เปิดการตั้งค่าโครงการของคุณใน คอนโซล Firebase
- ใน แอพของคุณ เลือกแอพที่ต้องการ
ในบานหน้าต่างด้านขวา ให้เลือกตัวเลือกเพื่อดาวน์โหลดไฟล์การกำหนดค่าสำหรับแอป Apple และ Android
สำหรับเว็บแอป ให้เลือก Config เพื่อแสดงค่าการกำหนดค่า
ในการสร้างไฟล์คีย์:
- เปิด บานหน้าต่างบัญชีบริการ ของ Google Cloud Console
- เลือกบัญชีบริการเริ่มต้นของ App Engine และใช้เมนูตัวเลือกทางด้านขวาเพื่อเลือก สร้างคีย์
- เมื่อได้รับแจ้ง ให้เลือก JSON สำหรับประเภทคีย์ แล้วคลิก สร้าง
หลังจากบันทึกไฟล์คีย์แล้ว ให้เริ่มต้น 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');
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 พารามิเตอร์:
- ข้อมูล (จำเป็น): ข้อมูลที่จะส่งไปยัง
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 คุณควรใช้วัตถุธรรมดาที่มี stubs แทนการสร้าง 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 เพื่อทำการยืนยันเหล่านี้
การยืนยันในโหมดออนไลน์
หากคุณเริ่มต้น 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. 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);
คุณยังสามารถใช้ สายลับ Sinon เพื่อยืนยันว่ามีการเรียกใช้เมธอดบางอย่าง และด้วยพารามิเตอร์ที่คุณคาดหวัง
การทดสอบฟังก์ชัน 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 ตั้งไว้เมื่อเริ่มต้น และลบแอป Firebase ที่อาจสร้างขึ้นหากคุณใช้ SDK เพื่อสร้าง DataSnapshot
หรือ Firestore DocumentSnapshot
ฐานข้อมูลเรียลไทม์
test.cleanup();
ตรวจสอบตัวอย่างที่สมบูรณ์และเรียนรู้เพิ่มเติม
คุณสามารถตรวจสอบตัวอย่างทั้งหมดได้ในที่เก็บ Firebase GitHub
- การทดสอบฐานข้อมูลเรียลไทม์และฟังก์ชัน HTTP ในโหมดออนไลน์
- การทดสอบฐานข้อมูลเรียลไทม์และฟังก์ชัน HTTP ในโหมดออฟไลน์
หากต้องการเรียนรู้เพิ่มเติม โปรดดู ข้อมูลอ้างอิง API สำหรับ firebase-functions-test