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. 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 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 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.
  3. 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:

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

Untuk membuat file kunci:

  • Buka panel Akun Layanan di Google Cloud Console.
  • Pilih akun layanan default App Engine, dan gunakan menu opsi di sebelah kanan untuk memilih Buat kunci.
  • 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() 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 Anda melakukan inisialisasi firebase-functions-test pada mode offline, dan memiliki admin.initializeApp() dalam kode fungsi, 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:

  • Gabungkan fungsi yang ingin diuji dengan metode test.wrap.
  • Buat data pengujian.
  • Aktifkan fungsi yang dikemas dengan data pengujian yang Anda buat dan setiap kolom konteks peristiwa yang ingin Anda tentukan.
  • Buat pernyataan tentang perilaku.
  • Pertama gabungkan fungsi yang ingin Anda uji. Misalnya, Anda memiliki fungsi functions/index.js bernama makeUppercase, yang ingin Anda uji. Tulis fungsi 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:

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

    Baca 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 menawarkan 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 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 dua kali:

  • snap.val()
  • snap.ref.parent.child('uppercase').set(uppercase)
  • Dalam kode pengujian, buat objek biasa di mana kedua lokasi kode berikut dapat berfungsi, lalu gunakan Sinon untuk men-stub metodenya.

    // 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, menggabung fungsi, dan membuat data, Anda dapat mengaktifkan fungsi yang digabung 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 tersebut:

    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 menginisialisasi Firebase Test SDK untuk Cloud Functions dalam mode online, Anda dapat menyatakan bahwa tindakan yang diinginkan (seperti penulisan database) telah dilakukan 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 Anda 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 asli dan 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 tidak hanya akan membatalkan penyetelan variabel lingkungan yang disetel oleh SDK saat diinisialisasi, tetapi juga menghapus aplikasi Firebase yang mungkin telah dibuat jika Anda menggunakan SDK untuk membuat DataSnapshot database real-time atau DocumentSnapshot Firestore.

    test.cleanup();
    

    Membaca contoh lengkap dan mempelajari lebih lanjut

    Anda dapat membaca contoh lengkapnya di repositori Firebase GitHub.

  • Menguji Database dan Fungsi HTTP pada Mode Offline
  • Menguji Database dan Fungsi HTTP pada Mode Online
  • Untuk mempelajari lebih lanjut, baca referensi API untuk firebase-functions-test.