Cloud Functions की यूनिट टेस्टिंग

इस पेज पर, आपके फ़ंक्शन के लिए यूनिट टेस्ट लिखने के सबसे सही तरीके और टूल के बारे में बताया गया है. जैसे, ऐसे टेस्ट जो कंटीन्यूअस इंटिग्रेशन (सीआई) सिस्टम का हिस्सा होंगे. टेस्टिंग को आसान बनाने के लिए, Firebase, Cloud Functions के लिए Firebase Test SDK उपलब्ध कराता है. यह npm पर firebase-functions-test के तौर पर डिस्ट्रिब्यूट किया जाता है. साथ ही, यह firebase-functions के लिए एक साथी टेस्ट SDK टूल है. Firebase Test SDK के लिए Cloud Functions:

  • आपके टेस्ट के लिए सही सेटअप और टियरडाउन का ध्यान रखता है. जैसे, firebase-functions के लिए ज़रूरी एनवायरमेंट वैरिएबल सेट करना और उन्हें अनसेट करना.
  • सैंपल डेटा और इवेंट कॉन्टेक्स्ट जनरेट करता है, ताकि आपको सिर्फ़ वे फ़ील्ड तय करने पड़ें जो आपके टेस्ट के लिए ज़रूरी हैं.

टेस्ट सेटअप

अपने फ़ंक्शन वाले फ़ोल्डर में, ये कमांड चलाकर firebase-functions-test और Mocha, दोनों को इंस्टॉल करें. 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 का इस्तेमाल दो तरीकों से किया जा सकता है:

  1. ऑनलाइन मोड (सुझाया जाता है): ऐसे टेस्ट लिखें जो टेस्टिंग के लिए बने Firebase प्रोजेक्ट के साथ इंटरैक्ट करते हैं, ताकि डेटाबेस में लिखने, उपयोगकर्ता बनाने वगैरह जैसी कार्रवाइयां असल में हों. साथ ही, आपका टेस्ट कोड नतीजों की जांच कर सके. इसका मतलब यह भी है कि आपके फ़ंक्शन में इस्तेमाल किए गए Google के अन्य SDK टूल भी काम करेंगे.
  2. ऑफ़लाइन मोड: अलग-अलग और ऑफ़लाइन यूनिट टेस्ट लिखें, जिनका कोई साइड इफ़ेक्ट न हो. इसका मतलब है कि Firebase प्रॉडक्ट के साथ इंटरैक्ट करने वाले किसी भी तरीके के कॉल (जैसे, डेटाबेस में लिखना या उपयोगकर्ता बनाना) को स्टब करना होगा. अगर आपके पास Cloud Firestore या Realtime Database फ़ंक्शन हैं, तो आम तौर पर ऑफ़लाइन मोड का इस्तेमाल करने का सुझाव नहीं दिया जाता. इसकी वजह यह है कि इससे आपके टेस्ट कोड की जटिलता काफ़ी बढ़ जाती है.

ऑनलाइन मोड में एसडीके शुरू करना (सुझाया जाता है)

अगर आपको ऐसे टेस्ट लिखने हैं जो टेस्ट प्रोजेक्ट के साथ इंटरैक्ट करते हैं, तो आपको प्रोजेक्ट कॉन्फ़िगरेशन की वे वैल्यू देनी होंगी जो firebase-admin के ज़रिए ऐप्लिकेशन को शुरू करने के लिए ज़रूरी हैं. साथ ही, सेवा खाते की कुंजी वाली फ़ाइल का पाथ भी देना होगा.

अपने Firebase प्रोजेक्ट की कॉन्फ़िगरेशन वैल्यू पाने के लिए:

  1. Firebase कंसोल में, अपने प्रोजेक्ट की सेटिंग खोलें.
  2. आपके ऐप्लिकेशन में,अपना पसंदीदा ऐप्लिकेशन चुनें.
  3. दाएं पैनल में, Apple और Android ऐप्लिकेशन के लिए कॉन्फ़िगरेशन फ़ाइल डाउनलोड करने का विकल्प चुनें.

    वेब ऐप्लिकेशन के लिए, कॉन्फ़िगरेशन वैल्यू दिखाने के लिए कॉन्फ़िगरेशन चुनें.

कुंजी वाली फ़ाइल बनाने के लिए:

  1. सेवा खाते वाला पैनल Google Cloud कंसोल का खोलें.
  2. App Engine डिफ़ॉल्ट सेवा खाते को चुनें. इसके बाद, दाएं ओर मौजूद विकल्पों के मेन्यू का इस्तेमाल करके, कुंजी बनाएं को चुनें.
  3. प्रॉम्प्ट मिलने पर, कुंजी के टाइप के लिए JSON चुनें. इसके बाद, बनाएं पर क्लिक करें.

कुंजी वाली फ़ाइल सेव करने के बाद, एसडीके शुरू करें:

// 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');

ऑफ़लाइन मोड में एसडीके शुरू करना

अगर आपको पूरी तरह से ऑफ़लाइन टेस्ट लिखने हैं, तो बिना किसी पैरामीटर के एसडीके शुरू किया जा सकता है:

// 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');

बैकग्राउंड (गैर-एचटीटीपी) फ़ंक्शन की टेस्टिंग

गैर-एचटीटीपी फ़ंक्शन की टेस्टिंग के लिए, यह तरीका अपनाएं:

  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 दो पैरामीटर लेता है:

  1. डेटा (ज़रूरी है): makeUppercase को भेजा जाने वाला डेटा. यह सीधे तौर पर, आपके लिखे गए फ़ंक्शन हैंडलर को भेजे गए पहले पैरामीटर से मेल खाता है. firebase-functions-test , कस्टम डेटा या सैंपल डेटा बनाने के तरीके उपलब्ध कराता है.
  2. eventContextOptions (ज़रूरी नहीं): इवेंट कॉन्टेक्स्ट के वे फ़ील्ड जिन्हें आपको तय करना है. इवेंट कॉन्टेक्स्ट, आपके लिखे गए फ़ंक्शन हैंडलर को भेजा गया दूसरा पैरामीटर है. अगर wrapped को कॉल करते समय, eventContextOptions पैरामीटर शामिल नहीं किया जाता है, तो भी समझदारी से चुने गए फ़ील्ड के साथ एक इवेंट कॉन्टेक्स्ट जनरेट होता है. यहां तय करके, जनरेट किए गए कुछ फ़ील्ड को बदला जा सकता है. ध्यान दें कि आपको सिर्फ़ वे फ़ील्ड शामिल करने होंगे जिन्हें बदलना है. वे सभी फ़ील्ड जनरेट किए जाते हैं जिन्हें आपने नहीं बदला है.
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 में, आपके फ़ंक्शन की टेस्टिंग के लिए ज़रूरी डेटा बनाने के कई फ़ंक्शन होते हैं. उदाहरण के लिए, Firestore DocumentSnapshot बनाने के लिए, test.firestore.makeDocumentSnapshot का इस्तेमाल करें. पहला आर्ग्युमेंट डेटा है और दूसरा आर्ग्युमेंट, रेफ़रंस का पूरा पाथ है. इसके अलावा, स्नैपशॉट की अन्य प्रॉपर्टी के लिए तीसरा आर्ग्युमेंट भी दिया जा सकता है.

// 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 फ़ंक्शन की टेस्टिंग की जा रही है, तो आपको दो स्नैपशॉट बनाने होंगे: एक पहले की स्थिति के लिए और दूसरा बाद की स्थिति के लिए. इसके बाद, इन स्नैपशॉट के साथ Change ऑब्जेक्ट बनाने के लिए, makeChange तरीके का इस्तेमाल किया जा सकता है.

// 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);

अन्य सभी डेटा टाइप के लिए, इसी तरह के फ़ंक्शन देखने के लिए, एपीआई के बारे में जानकारी देखें.

सैंपल डेटा का इस्तेमाल करना

अगर आपको अपने टेस्ट में इस्तेमाल किए गए डेटा को पसंद के मुताबिक बनाने की ज़रूरत नहीं है, तो 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();

हर फ़ंक्शन टाइप के लिए, सैंपल डेटा पाने के तरीके जानने के लिए, एपीआई के बारे में जानकारी देखें.

स्टब किए गए डेटा का इस्तेमाल करना (ऑफ़लाइन मोड के लिए)

अगर एसडीके को ऑफ़लाइन मोड में शुरू किया गया है और Cloud Firestore या Realtime Database फ़ंक्शन की टेस्टिंग की जा रही है, तो असली 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);

दावे करना

एसडीके शुरू करने, फ़ंक्शन को रैप करने, और डेटा बनाने के बाद, बनाए गए डेटा के साथ रैप किए गए फ़ंक्शन को कॉल किया जा सकता है. साथ ही, व्यवहार के बारे में दावे किए जा सकते हैं. इन दावों को करने के लिए, Chai जैसी लाइब्रेरी का इस्तेमाल किया जा सकता है.

ऑनलाइन मोड में दावे करना

अगर Cloud Functions के लिए Firebase Test SDK को ऑनलाइन मोड में शुरू किया गया है, तो 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 स्पाइज़ का इस्तेमाल करके, यह दावा भी किया जा सकता है कि कुछ तरीके कॉल किए गए हैं और वे पैरामीटर इस्तेमाल किए गए हैं जिनकी उम्मीद थी.

एचटीटीपी फ़ंक्शन की टेस्टिंग

एचटीटीपी onCall फ़ंक्शन की टेस्टिंग के लिए, बैकग्राउंड फ़ंक्शन की टेस्टिंग वाला तरीका अपनाएं.

अगर एचटीटीपी onRequest फ़ंक्शन की टेस्टिंग की जा रही है, तो firebase-functions-test का इस्तेमाल तब करें, जब:

  • functions.config() का इस्तेमाल किया जाता है
  • आपका फ़ंक्शन, Firebase प्रोजेक्ट या Google के अन्य एपीआई के साथ इंटरैक्ट करता है. साथ ही, आपको अपने टेस्ट के लिए, असली Firebase प्रोजेक्ट और उसके क्रेडेंशियल का इस्तेमाल करना है.

एचटीटीपी 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);

टेस्ट क्लीनअप

अपने टेस्ट कोड के आखिर में, क्लीनअप फ़ंक्शन को कॉल करें. इससे, एसडीके को शुरू करते समय सेट किए गए एनवायरमेंट वैरिएबल अनसेट हो जाते हैं. साथ ही, Firebase के उन ऐप्लिकेशन को मिटा दिया जाता है जो तब बनाए गए हो सकते हैं, जब एसडीके का इस्तेमाल करके, रीयल टाइम डेटाबेस DataSnapshot या Firestore DocumentSnapshot बनाया गया हो.

test.cleanup();

पूरे उदाहरण देखें और ज़्यादा जानें

Firebase GitHub रिपॉज़िटरी पर, पूरे उदाहरण देखे जा सकते हैं.

ज़्यादा जानने के लिए, एपीआई के बारे में जानकारी देखें firebase-functions-test.