اختبار وحدة وظائف السحابة

تصف هذه الصفحة أفضل الممارسات والأدوات لكتابة اختبارات الوحدة لوظائفك ، مثل الاختبارات التي ستكون جزءًا من نظام التكامل المستمر (CI). لتسهيل الاختبار ، يوفر Firebase حزمة Firebase Test SDK لوظائف السحابة. يتم توزيعه على الآلية الوقائية الوطنية كما firebase-functions-test ، وهو رفيق اختبار SDK ل firebase-functions . حزمة تطوير البرامج (SDK) الخاصة باختبار Firebase لوظائف السحابة:

  • يعتني الإعداد المناسب وteardown للاختبارات الخاصة بك، مثل وضع والبيئة unsetting المتغيرات التي يحتاجها firebase-functions .
  • يولد عينة من البيانات وسياق الحدث ، بحيث يكون عليك فقط تحديد الحقول ذات الصلة بالاختبار الخاص بك.

إعداد الاختبار

تثبيت كل firebase-functions-test و موكا ، إطار الاختبار، عن طريق تشغيل الأوامر التالية في وظائف مجلد:

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. وضع دون اتصال: كتابة siled وغير متصل وحدة الاختبارات مع أي آثار جانبية. هذا يعني أن أي استدعاءات طريقة تتفاعل مع منتج Firebase (مثل الكتابة إلى قاعدة البيانات أو إنشاء مستخدم) يجب إيقافها. لا يُنصح عمومًا باستخدام الوضع غير المتصل إذا كانت لديك وظائف Cloud Firestore أو Realtime Database ، نظرًا لأنها تزيد بشكل كبير من تعقيد كود الاختبار الخاص بك.

تهيئة SDK في وضع الاتصال (موصى به)

إذا كنت ترغب في اختبارات الكتابة التي تتفاعل مع مشروع اختبار، تحتاج إلى توفير القيم التكوين المشروع الذي اللازمة لتهيئة التطبيق من خلال firebase-admin ، والمسار إلى ملف مفتاح حساب الخدمة.

للحصول على قيم تكوين مشروع Firebase:

  1. فتح إعدادات المشروع الخاص بك في وحدة التحكم Firebase .
  2. في تطبيقاتك، حدد التطبيق المطلوب.
  3. في الجزء الأيسر ، حدد خيار تنزيل ملف تكوين لتطبيقات iOS و Android.

    لتطبيقات الويب، اختر تكوين لعرض قيم التكوين.

لإنشاء ملف مفتاح:

  1. فتح جزء حسابات خدمة وحدة التحكم سحابة جوجل.
  2. حدد حساب الخدمة الافتراضية محرك التطبيقات، واستخدام قائمة الخيارات على اليمين لتحديد إنشاء مفتاح.
  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 لطرق للحصول على البيانات مثال لكل نوع وظيفة.

استخدام بيانات stubbed (للوضع غير المتصل)

إذا كنت تهيئة SDK في وضع غير متصل بالشبكة، ويتم اختبار وظيفة قاعدة البيانات سحابة Firestore أو الحقيقي، يجب عليك استخدام كائن عادي مع بذرة بدلا من إنشاء الفعلية 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)

في رمز اختبار، إنشاء كائن عادي حيث كل من هذه المسارات كود ستعمل، واستخدام سينون إلى كعب الطرق.

// 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 ، وتغليف الوظائف ، وإنشاء البيانات ، يمكنك استدعاء الوظائف المُغلفة بالبيانات المُنشأة وإجراء تأكيدات حول السلوك. يمكنك استخدام هذه المكتبة كما تشاي لجعل هذه التأكيدات.

جعل التأكيدات في وضع الاتصال بالإنترنت

إذا كنت تهيئة Firebase اختبار 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 assert.equal(wrapped(snap), true);

يمكنك أيضا استخدام جواسيس سينون التأكيد على أن أساليب معينة قد دعا، ومع المعلمات التي تتوقعها.

اختبار وظائف HTTP

لاختبار وظائف HTTP onCall، استخدم نفس النهج اختبار وظائف أساسية .

إذا كنت اختبار وظائف HTTP onRequest، يجب عليك استخدام firebase-functions-test إذا:

  • يمكنك استخدام functions.config()
  • تتفاعل وظيفتك مع مشروع Firebase أو واجهات برمجة تطبيقات Google أخرى ، وتريد استخدام مشروع 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);

اختبار التنظيف

في نهاية كود الاختبار الخاص بك ، اتصل بوظيفة التنظيف. هذا unsets متغيرات البيئة أن مجموعة SDK عندما تمت تهيئة ذلك، وحذف التطبيقات Firebase التي قد تم إنشاؤها إذا كنت تستخدم SDK لإنشاء قاعدة بيانات في الوقت الحقيقي DataSnapshot أو Firestore DocumentSnapshot .

test.cleanup();

راجع الأمثلة الكاملة وتعلم المزيد

يمكنك مراجعة الأمثلة الكاملة على مستودع Firebase GitHub.

لمعرفة المزيد، يرجى الرجوع إلى مرجع API ل firebase-functions-test .