Buka konsol

Pengujian unit Cloud Functions

Halaman ini akan menguraikan praktik terbaik dan fitur untuk menulis pengujian unit untuk fungsi Anda, seperti pengujian yang akan menjadi bagian dari sistem Continuous Integration (CI). Untuk mempermudah pengujian, Firebase menyediakan Firebase Test SDK untuk Cloud Functions. Firebase Test SDK ini didistribusikan di npm sebagai firebase-functions-test, dan merupakan SDK pengujian pendamping untuk firebase-functions. Firebase Test SDK untuk Cloud Functions:

  • Menangani penyiapan dan pembongkaran yang tepat untuk pengujian, seperti menetapkan dan membatalkan penetapan variabel lingkungan yang dibutuhkan oleh firebase-functions.
  • Menghasilkan data sampel dan konteks peristiwa sehingga Anda hanya perlu menentukan kolom yang relevan untuk pengujian Anda.

Penyiapan pengujian

Instal firebase-functions-test dan Mocha, yang merupakan framework pengujian, dengan menjalankan perintah berikut di folder fungsi Anda:

npm install --save-dev firebase-functions-test
npm install --save-dev mocha

Lalu buat folder test di dalam folder fungsi, buat file baru di dalamnya untuk kode pengujian Anda, dan beri nama seperti index.test.js.

Terakhir, ubah functions/package.json untuk menambahkan berikut ini:

"scripts": {
  "test": "mocha --reporter spec"
}

Setelah menulis pengujian, Anda dapat menjalankannya dengan menjalankan npm test di dalam direktori fungsi Anda.

Menginisialisasi Firebase Test SDK untuk Cloud Functions

Ada 2 cara untuk menggunakan firebase-functions-test:

  1. Mode offline: Menulis pengujian unit terisolasi dan offline tanpa efek samping. Ini berarti bahwa pemanggilan metode apa pun yang berinteraksi dengan produk Firebase (misalnya, menulis ke database atau membuat pengguna) harus diberi stub.
  2. Mode online: Menulis pengujian yang berinteraksi dengan project Firebase yang dikhususkan untuk pengujian sehingga penulisan database, pembuatan pengguna, dan sebagainya akan terjadi, dan kode pengujian Anda dapat memeriksa hasilnya. Ini juga berarti bahwa SDK Google lain yang digunakan pada fungsi Anda juga akan berfungsi.

Menginisialisasi SDK pada mode offline

Jika ingin menulis pengujian yang sepenuhnya offline, Anda dapat menginisialisasi SDK tanpa parameter apa pun:

// At the top of test/index.test.js
const test = require('firebase-functions-test')();

Menginisialisasi SDK pada mode online

Jika ingin menulis pengujian yang berinteraksi dengan project pengujian, Anda harus memasukkan nilai konfigurasi project yang diperlukan untuk menginisialisasi aplikasi melalui firebase-admin, dan lokasi ke file kunci akun layanan.

Untuk mendapatkan nilai konfigurasi project Firebase Anda:

  1. Buka Firebase Console,
  2. Pilih project Anda, lalu klik Tambahkan Firebase ke aplikasi web, nilai konfigurasi ditampilkan di jendela pop-up.

Untuk membuat file kunci:

  1. Buka panel Akun Layanan di Google Cloud Console.
  2. Pilih akun layanan default App Engine, dan gunakan menu opsi di sebelah kanan untuk memilih Buat kunci.
  3. Saat diminta, pilih JSON untuk jenis kunci, lalu klik Buat.

Setelah menyimpan file kunci, inisialisasi 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');

Membuat nilai konfigurasi fiktif

Jika menggunakan functions.config() pada kode fungsi, Anda dapat membuat nilai konfigurasi fiktif. Misalnya, jika functions/index.js berisi kode berikut:

const functions = require('firebase-functions');
const key = functions.config().stripe.key;

Maka Anda dapat membuat nilai fiktif untuk nilai yang berada di dalam file pengujian seperti berikut:

// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});

Mengimpor fungsi

Untuk mengimpor fungsi, gunakan require untuk mengimpor file fungsi utama Anda sebagai modul. Pastikan untuk melakukan ini hanya setelah menginisialisasi firebase-functions-test, dan membuat nilai konfigurasi fiktif.

// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code

Jika Anda menginisialisasi firebase-functions-test pada mode offline dan memiliki admin.initializeApp() dalam kode function, Anda harus memberinya stub sebelum mengimpor fungsi tersebut:

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

Menguji fungsi latar belakang (non-HTTP)

Proses untuk menguji fungsi non-HTTP melibatkan langkah-langkah berikut:

  1. Kemas fungsi yang ingin diuji dengan metode test.wrap
  2. Buat data pengujian
  3. Aktifkan fungsi yang dikemas dengan data pengujian yang Anda buat dan setiap kolom konteks peristiwa yang ingin Anda tentukan.
  4. Buat pernyataan tentang perilaku.

Pertama-tama, kemas fungsi yang ingin diuji. Misalkan Anda memiliki fungsi dalam functions/index.js dengan nama makeUppercase yang ingin diuji. Tuliskan berikut ini dalam functions/test/index.test.js

// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);

wrapped adalah fungsi yang mengaktifkan makeUppercase ketika dipanggil. wrapped memerlukan 2 parameter:

  1. data (wajib): data yang akan dikirim ke makeUppercase. Data ini berkaitan langsung dengan parameter pertama yang dikirim ke penangan fungsi yang Anda tulis. firebase-functions-test menyediakan metode untuk membuat data kustom atau data contoh.
  2. eventContextOptions (opsional): kolom konteks peristiwa yang ingin ditentukan. Konteks peristiwa adalah parameter kedua yang dikirimkan ke penangan fungsi yang Anda tulis. Jika Anda tidak menyertakan parameter eventContextOptions ketika memanggil wrapped, konteks peristiwa tetap akan dihasilkan dengan kolom yang logis. Anda dapat mengganti beberapa kolom yang dihasilkan dengan menentukannya di sini. Perhatikan bahwa Anda hanya perlu menyertakan kolom yang ingin diganti. Semua kolom yang tidak Anda ganti akan dihasilkan.
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
});

Membuat data pengujian

Parameter pertama dari fungsi yang dikemas adalah data pengujian untuk mengaktifkan fungsi yang mendasarinya. Ada sejumlah cara untuk membuat data pengujian.

Menggunakan data kustom

firebase-functions-test memiliki sejumlah fungsi untuk membuat data yang diperlukan untuk menguji fungsi Anda. Misalnya, gunakan test.firestore.makeDocumentSnapshot untuk membuat DocumentSnapshot Firestore. Argumen pertama adalah data, dan argumen kedua adalah lokasi referensi lengkap, dan ada argumen ketiga opsional untuk properti lain dari snapshot yang dapat Anda tentukan.

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

Jika Anda menguji fungsi onUpdate atau onWrite, Anda harus membuat 2 snapshot: 1 untuk status sebelumnya dan 1 lagi untuk status setelahnya. Kemudian, Anda dapat menggunakan metode makeChange untuk membuat objek Change dengan spanshot ini.

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

Lihat referensi API untuk fungsi serupa untuk semua jenis data lainnya.

Menggunakan data contoh

Jika Anda tidak perlu menyesuaikan data yang digunakan dalam pengujian, maka firebase-functions-test menawarkan metode untuk menghasilkan data contoh untuk setiap jenis fungsi.

// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();

Lihat referensi API untuk mengetahui metode mendapatkan data contoh untuk setiap jenis fungsi.

Menggunakan data yang diberi stub (untuk mode offline)

Jika Anda menginisialisasi SDK dalam mode offline, dan sedang menguji fungsi Firestore atau Database, mungkin akan lebih mudah jika Anda menggunakan objek biasa dengan stub, bukannya membuat DocumentSnapshot atau DataSnapshot sebenarnya lalu men-stub metodenya.

Misalkan Anda sedang menulis pengujian unit untuk fungsi berikut:

// 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();
      console.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);
    });

Di dalam fungsi, snap digunakan 2 kali:

  • snap.val()
  • snap.ref.parent.child('uppercase').set(uppercase)

Dalam kode pengujian, buat objek biasa di mana kedua lokasi kode berikut dapat berfungsi, dan gunakan Sinon untuk men-stub metode.

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

Membuat pernyataan

Setelah menginisialisasi SDK, mengemas fungsi, dan membuat data, Anda dapat mengaktifkan fungsi yang dikemas dengan data yang telah dibuat dan membuat pernyataan tentang perilaku. Anda dapat menggunakan library seperti Chai untuk membuat pernyataan ini.

Membuat pernyataan pada mode offline

Anda dapat membuat pernyataan tentang nilai hasil yang diharapkan dari fungsi:

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

Anda juga dapat menggunakan Sinon spies untuk membuat pernyataan bahwa metode tertentu telah dipanggil, dan dengan parameter yang diharapkan.

Membuat pernyataan pada mode online

Jika Anda menginisialisasi Firebase Test SDK untuk Cloud Functions dalam mode online, Anda dapat menyatakan bahwa tindakan yang diinginkan (seperti penulisan database) telah terjadi dengan menggunakan SDK firebase-admin.

Contoh di bawah ini menyatakan bahwa 'INPUT' telah ditulis ke dalam database real-time dari project pengujian.

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

Menguji fungsi HTTP

Jika menguji fungsi HTTP onRequest, Anda harus menggunakan firebase-functions-test jika:

  • Anda menggunakan functions.config()
  • Fungsi Anda berinteraksi dengan project Firebase atau Google API lainnya, dan Anda ingin menggunakan project Firebase asli dan kredensialnya untuk pengujian.

Fungsi HTTP onRequest memerlukan 2 parameter: objek request dan objek response. Berikut cara menguji fungsi contoh addMessage():

  • Ganti fungsi pengalihan pada objek response, karena sendMessage() memanggilnya.
  • Dalam fungsi pengalihan ini, gunakan chai.assert untuk membantu membuat pernyataan tentang parameter yang akan digunakan untuk memanggil fungsi pengalihan:
// 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);

Pembersihan pengujian

Di bagian paling akhir dari kode pengujian Anda, panggil fungsi pembersihan. Fungsi ini akan membatalkan penetapan variabel lingkungan yang ditetapkan oleh SDK saat diinisialisasi, dan menghapus aplikasi Firebase yang mungkin telah dibuat jika Anda menggunakan SDK untuk membuat DataSnapshot database real-time atau DocumentSnapshot Firestore.

test.cleanup();

Meninjau contoh lengkap dan mempelajari lebih lanjut

Anda dapat meninjau contoh lengkap di repositori Firebase GitHub.

Untuk mempelajari lebih lanjut, baca referensi API untuk firebase-functions-testfirebase-functions-test.