หน้านี้จะอธิบายถึงแนวทางปฏิบัติที่ดีที่สุดและเครื่องมือในการเขียน การทดสอบ 1 หน่วยสำหรับ
เช่น การทดสอบที่จะเป็นส่วนหนึ่งของการรวมอย่างต่อเนื่อง (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 เฉพาะสำหรับการทดสอบเพื่อให้เขียนฐานข้อมูล สร้างโดยผู้ใช้ และอื่นๆ และโค้ดทดสอบจะตรวจสอบผลลัพธ์ได้ และยังหมายความว่า Google SDK ที่ใช้ในฟังก์ชันของคุณก็จะทํางานได้เช่นกัน
- โหมดออฟไลน์: เขียนการทดสอบหน่วยแบบแยกส่วนและออฟไลน์โดยไม่มีผลข้างเคียง ซึ่งหมายความว่าการเรียกใช้ทุกเมธอดที่โต้ตอบกับผลิตภัณฑ์ Firebase (เช่น เขียนไปยังฐานข้อมูลหรือสร้างผู้ใช้) ต้องตัดออก ใช้งานแบบออฟไลน์ เราไม่แนะนำให้ใช้โหมดนี้หากคุณมี Cloud Firestore หรือ Realtime Database เนื่องจากจะเพิ่มความซับซ้อนของโค้ดทดสอบเป็นอย่างมาก
เริ่มต้น SDK ในโหมดออนไลน์ (แนะนำ)
หากต้องการเขียนการทดสอบที่โต้ตอบกับโปรเจ็กต์ทดสอบ คุณจะต้อง
ระบุค่าการกำหนดค่าโปรเจ็กต์ที่จำเป็นสำหรับการเริ่มต้นแอปผ่าน
firebase-admin
และเส้นทางไปยังไฟล์คีย์บัญชีบริการ
วิธีรับค่าการกําหนดค่าของโปรเจ็กต์ Firebase
- เปิดการตั้งค่าโปรเจ็กต์ใน คอนโซล Firebase
- ในแอปของคุณ ให้เลือกแอปที่ต้องการ
ในแผงด้านขวา ให้เลือกตัวเลือกเพื่อดาวน์โหลดไฟล์การกำหนดค่า สำหรับแอป Apple และ Android
สําหรับเว็บแอป ให้เลือกกําหนดค่าเพื่อแสดง การกำหนดค่า
วิธีสร้างไฟล์คีย์
- เปิดแผงบัญชีบริการ ของคอนโซล Google Cloud
- เลือกบัญชีบริการเริ่มต้นของ 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/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
เพื่อสร้าง Firestore DocumentSnapshot
อาร์กิวเมนต์แรกคือข้อมูล และ
อาร์กิวเมนต์ที่สองคือเส้นทางการอ้างอิงแบบเต็ม และมี
อาร์กิวเมนต์ที่ 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
คุณจะต้องสร้าง
สแนปชอต 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 คุณควรใช้ออบเจ็กต์ธรรมดาที่มีสตับ
แทนการสร้าง DocumentSnapshot
หรือ DataSnapshot
จริง
สมมติว่าคุณกำลังเขียนการทดสอบ 1 หน่วยสำหรับฟังก์ชันต่อไปนี้
// 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)
ในโค้ดทดสอบ ให้สร้างออบเจ็กต์ทั่วไปที่เส้นทางโค้ดทั้งสองนี้จะทำงาน และใช้ 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 assert.equal(wrapped(snap), true);
นอกจากนี้คุณยังสามารถใช้สายลับของ Sinon เพื่อ ยืนยันว่ามีการเรียกเมธอดบางอย่างและมีพารามิเตอร์ที่คุณต้องการ
การทดสอบฟังก์ชัน 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
หรือ Firestore DocumentSnapshot
test.cleanup();
ดูตัวอย่างที่สมบูรณ์และดูข้อมูลเพิ่มเติม
คุณดูตัวอย่างทั้งหมดในที่เก็บ Firebase GitHub ได้
- การทดสอบ Realtime Database และฟังก์ชัน HTTP ในโหมดออนไลน์
- การทดสอบ Realtime Database และฟังก์ชัน HTTP ในโหมดออฟไลน์
ดูข้อมูลเพิ่มเติมได้ในเอกสารอ้างอิง API
เป็นเวลา firebase-functions-test