Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

การทดสอบหน่วยของ Cloud Functions

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

หน้านี้อธิบายแนวทางปฏิบัติที่ดีที่สุดและเครื่องมือสำหรับการเขียนการทดสอบหน่วยสำหรับฟังก์ชันของคุณ เช่น การทดสอบที่จะเป็นส่วนหนึ่งของระบบบูรณาการต่อเนื่อง (CI) เพื่อให้การทดสอบง่ายขึ้น Firebase มี Firebase Test SDK สำหรับ Cloud Functions มีการแจกจ่ายใน npm เป็น firebase 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 :

  1. โหมดออนไลน์ (แนะนำ): การทดสอบการเขียนที่โต้ตอบกับโปรเจ็กต์ Firebase สำหรับการทดสอบโดยเฉพาะ เพื่อให้การเขียนฐานข้อมูล ผู้ใช้สร้าง และอื่นๆ เกิดขึ้นจริง และโค้ดทดสอบของคุณสามารถตรวจสอบผลลัพธ์ได้ นอกจากนี้ยังหมายความว่า Google SDK อื่นๆ ที่ใช้ในฟังก์ชันของคุณจะใช้งานได้เช่นกัน
  2. โหมดออฟไลน์: เขียนการทดสอบแบบแยกส่วนและแบบออฟไลน์โดยไม่มีผลข้างเคียง ซึ่งหมายความว่าการเรียกเมธอดใดๆ ที่โต้ตอบกับผลิตภัณฑ์ Firebase (เช่น การเขียนไปยังฐานข้อมูลหรือการสร้างผู้ใช้) จะต้องถูกระงับ โดยทั่วไปไม่แนะนำให้ใช้โหมดออฟไลน์หากคุณมีฟังก์ชัน Cloud Firestore หรือฐานข้อมูลเรียลไทม์ เนื่องจากจะเพิ่มความซับซ้อนของโค้ดทดสอบอย่างมาก

เริ่มต้น SDK ในโหมดออนไลน์ (แนะนำ)

หากคุณต้องการเขียนการทดสอบที่โต้ตอบกับโปรเจ็กต์ทดสอบ คุณต้องระบุค่าคอนฟิกโปรเจ็กต์ที่จำเป็นสำหรับการเริ่มต้นแอปผ่าน firebase-admin และพาธไปยังไฟล์คีย์ของบัญชีบริการ

วิธีรับค่ากำหนดของโครงการ Firebase:

  1. เปิดการตั้งค่าโครงการของคุณใน คอนโซล Firebase
  2. ใน แอพของคุณ เลือกแอพที่ต้องการ
  3. ในบานหน้าต่างด้านขวา ให้เลือกตัวเลือกเพื่อดาวน์โหลดไฟล์การกำหนดค่าสำหรับแอป Apple และ Android

    สำหรับเว็บแอป ให้เลือก Config เพื่อแสดงค่าการกำหนดค่า

ในการสร้างไฟล์คีย์:

  1. เปิด บานหน้าต่างบัญชีบริการ ของ Google Cloud Console
  2. เลือกบัญชีบริการเริ่มต้นของ App Engine และใช้เมนูตัวเลือกทางด้านขวาเพื่อเลือก สร้างคีย์
  3. เมื่อได้รับแจ้ง ให้เลือก 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 เกี่ยวข้องกับขั้นตอนต่อไปนี้:

  1. ล้อมฟังก์ชันที่คุณต้องการทดสอบด้วยวิธี test.wrap
  2. สร้างข้อมูลการทดสอบ
  3. เรียกใช้ฟังก์ชันที่รวมข้อมูลการทดสอบที่คุณสร้างและฟิลด์บริบทเหตุการณ์ที่คุณต้องการระบุ
  4. ยืนยันพฤติกรรม.

ก่อนอื่นให้รวมฟังก์ชันที่คุณต้องการทดสอบ สมมติว่าคุณมีฟังก์ชันใน 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 พารามิเตอร์:

  1. ข้อมูล (จำเป็น): ข้อมูลที่จะส่งไปยัง makeUppercase สิ่งนี้สอดคล้องโดยตรงกับพารามิเตอร์แรกที่ส่งไปยังตัวจัดการฟังก์ชันที่คุณเขียน firebase-functions-test มีวิธีการสร้างข้อมูลที่กำหนดเองหรือข้อมูลตัวอย่าง
  2. 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

หากต้องการเรียนรู้เพิ่มเติม โปรดอ้างอิง ถึงข้อมูลอ้างอิง API สำหรับ firebase-functions-test