Pengujian unit Cloud Functions

Halaman ini menguraikan berbagai fitur dan praktik terbaik 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. Alat 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 dengan pengujian yang dilakukan.

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, misalnya index.test.js.

Terakhir, ubah functions/package.json untuk menambahkan hal 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 dua cara untuk menggunakan firebase-functions-test:

  1. Mode online (direkomendasikan): Tulis pengujian yang berinteraksi dengan project Firebase yang dikhususkan untuk pengujian sehingga penulisan database, pembuatan pengguna, dan lain-lain akan benar-benar dilakukan, dan kode pengujian Anda dapat memeriksa hasilnya. Ini juga berarti bahwa SDK Google lain yang digunakan pada fungsi Anda juga akan berfungsi.
  2. Mode offline: Tulis pengujian unit terisolasi dan offline tanpa efek samping. Ini berarti bahwa panggilan metode apa pun yang berinteraksi dengan produk Firebase (misalnya, menulis ke database atau membuat pengguna) harus dijadikan stub. Penggunaan mode offline biasanya tidak direkomendasikan jika Anda memiliki fungsi Cloud Firestore atau Realtime Database, karena sangat meningkatkan kompleksitas kode pengujian Anda.

Menginisialisasi SDK dalam mode online (direkomendasikan)

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

Untuk mendapatkan nilai konfigurasi project Firebase Anda:

  1. Buka setelan project Anda di Firebase console.
  2. Di bagian Your apps, pilih aplikasi yang diinginkan.
  3. Di panel sebelah kanan, pilih opsi untuk mendownload file konfigurasi untuk aplikasi Apple dan Android.

    Untuk aplikasi web, pilih Config untuk menampilkan nilai konfigurasi.

Untuk membuat file kunci:

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

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

Menginisialisasi SDK dalam 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')();

Membuat nilai konfigurasi fiktif

Jika menggunakan functions.config() dalam 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 melakukan inisialisasi firebase-functions-test dalam mode offline dan memiliki admin.initializeApp() dalam kode fungsi, Anda harus menjadikannya stub sebelum mengimpor fungsi:

// 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 meliputi langkah-langkah berikut:

  1. Gabungkan fungsi yang ingin diuji dengan metode test.wrap.
  2. Buat data pengujian.
  3. Panggil fungsi gabungan dengan data pengujian yang Anda buat dan kolom konteks peristiwa yang ingin Anda tentukan.
  4. Buat pernyataan tentang perilaku.

Pertama gabungkan fungsi yang ingin Anda uji. Misalnya, Anda memiliki fungsi di dalam functions/index.js bernama makeUppercase, yang ingin Anda uji. Tulis hal berikut di 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 saat dipanggil. wrapped membutuhkan 2 parameter:

  1. data (wajib): data yang akan dikirimkan ke makeUppercase. Data ini berkaitan langsung dengan parameter pertama yang dikirim ke pengendali 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 pengendali 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 gabungan adalah data pengujian yang digunakan untuk memanggil 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 datanya, argumen kedua adalah jalur referensi lengkapnya, dan Anda dapat menentukan argumen ketiga opsional untuk properti lain snapshot.

// 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 menguji fungsi onUpdate atau onWrite, Anda harus membuat dua snapshot: satu untuk status sebelumnya dan satu lagi untuk status setelahnya. Kemudian, Anda dapat menggunakan metode makeChange untuk membuat objek Change dengan snapshot tersebut.

// 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 mengetahui fungsi serupa bagi semua jenis data lainnya.

Menggunakan data contoh

Jika Anda tidak perlu menyesuaikan data yang digunakan dalam pengujian, firebase-functions-test menyediakan metode untuk menghasilkan data contoh bagi 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();

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

Menggunakan data yang dijadikan stub (untuk mode offline)

Jika melakukan inisialisasi SDK dalam mode offline dan menguji fungsi Cloud Firestore atau Realtime Database, Anda harus menggunakan objek biasa dengan stub, bukannya membuat DocumentSnapshot atau DataSnapshot sebenarnya.

Misalnya 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();
      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);
    });

Di dalam fungsi, snap digunakan dua kali:

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

Dalam kode pengujian, buat objek biasa di mana kedua jalur kode tersebut dapat berfungsi, dan gunakan Sinon untuk membuat metodenya menjadi 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);

Membuat pernyataan

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

Membuat pernyataan dalam mode online

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

Contoh di bawah ini menyatakan bahwa 'INPUT' telah ditulis ke dalam database 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');
  });
});

Membuat pernyataan dalam 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 spy Sinon untuk membuat pernyataan bahwa metode tertentu telah dipanggil, dan dengan parameter yang diharapkan.

Menguji fungsi HTTP

Untuk menguji fungsi HTTP onCall, gunakan pendekatan yang sama dengan pengujian fungsi latar belakang.

Jika akan menguji fungsi HTTP onRequest, sebaiknya gunakan firebase-functions-test jika:

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

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

  • Ganti fungsi pengalihan pada objek response, karena dipanggil oleh sendMessage().
  • 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 membatalkan setelan variabel lingkungan yang ditetapkan oleh SDK saat diinisialisasi, dan menghapus aplikasi Firebase yang mungkin telah dibuat jika Anda menggunakan SDK untuk membuat DataSnapshot Realtime Database atau DocumentSnapshot Firestore.

test.cleanup();

Meninjau contoh lengkap dan mempelajari lebih lanjut

Anda dapat meninjau contoh lengkap di repositori GitHub Firebase.

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