Cloud Functions birim testi

Bu sayfada, işlevleriniz için birim testleri yazmaya yönelik en iyi uygulamalar ve araçlar (ör. sürekli entegrasyon (CI) sisteminin parçası olacak testler) açıklanmaktadır. Firebase, testi kolaylaştırmak için Firebase Test SDK Cloud Functions sağlar. npm'de firebase-functions-test olarak dağıtılır ve firebase-functions için yardımcı test SDK'sıdır. Cloud Functions için Firebase Test SDK:

  • firebase-functions tarafından gereken ortam değişkenlerini ayarlama ve kaldırma gibi testleriniz için uygun kurulum ve kaldırma işlemlerini yapar.
  • Yalnızca testinizle alakalı alanları belirtmeniz için örnek veriler ve etkinlik bağlamı oluşturur.

Test kurulumu

İşlevler klasörünüzde aşağıdaki komutları çalıştırarak hem firebase-functions-test hem de bir test çerçevesi olan Mocha'yı yükleyin:

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

Ardından, functions klasörünün içinde bir test klasörü oluşturun, bu klasörde test kodunuz için yeni bir dosya oluşturun ve dosyayı index.test.js gibi bir adla kaydedin.

Son olarak, functions/package.json dosyasını aşağıdaki bilgileri ekleyecek şekilde değiştirin:

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

Testleri yazdıktan sonra npm test komutunu işlevler dizininizde çalıştırarak testleri gerçekleştirebilirsiniz.

Cloud Functions için Firebase Test SDK başlatılıyor

firebase-functions-test öğesini kullanmanın iki yolu vardır:

  1. Çevrimiçi mod (önerilir): Veritabanı yazma işlemleri, kullanıcı oluşturma vb. işlemlerin gerçekten gerçekleşmesi ve test kodunuzun sonuçları inceleyebilmesi için test etmeye ayrılmış bir Firebase projesiyle etkileşime giren testler yazın. Bu, işlevlerinizde kullanılan diğer Google SDK'larının da çalışacağı anlamına gelir.
  2. Çevrimdışı mod: Yan etkileri olmayan, izole ve çevrimdışı birim testleri yazın. Bu, bir Firebase ürünüyle etkileşimde bulunan tüm yöntem çağrılarının (ör. veritabanına yazma veya kullanıcı oluşturma) sahte olması gerektiği anlamına gelir. Cloud Firestore veya Realtime Database işlevleriniz varsa test kodunuzun karmaşıklığını büyük ölçüde artırdığı için çevrimdışı modu kullanmanız genellikle önerilmez.

SDK'yı online modda başlatma (önerilir)

Bir test projesiyle etkileşimde bulunan testler yazmak istiyorsanız firebase-admin aracılığıyla uygulamayı başlatmak için gereken proje yapılandırma değerlerini ve bir hizmet hesabı anahtar dosyasının yolunu sağlamanız gerekir.

Firebase projenizin yapılandırma değerlerini almak için:

  1. Firebase konsolunda proje ayarlarınızı açın.
  2. Uygulamalarınız bölümünde istediğiniz uygulamayı seçin.
  3. Sağ bölmede, Apple ve Android uygulamaları için yapılandırma dosyası indirme seçeneğini belirleyin.

    Web uygulamaları için yapılandırma değerlerini görüntülemek üzere Yapılandırma'yı seçin.

Anahtar dosyası oluşturmak için:

  1. Google Cloud konsolunun Hizmet Hesapları bölmesini açın.
  2. App Engine varsayılan hizmet hesabını seçin ve sağdaki seçenekler menüsünü kullanarak Anahtar oluştur'u belirleyin.
  3. İstendiğinde anahtar türü olarak JSON'ı seçin ve Oluştur'u tıklayın.

Anahtar dosyasını kaydettikten sonra SDK'yı başlatın:

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

SDK'yı çevrimdışı modda başlatma

Tamamen çevrimdışı testler yazmak isterseniz SDK'yı herhangi bir parametre olmadan başlatabilirsiniz:

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

Yapılandırma değerlerini taklit etme

İşlev kodunuzda functions.config() kullanıyorsanız yapılandırma değerlerini taklit edebilirsiniz. Örneğin, functions/index.js aşağıdaki kodu içeriyorsa:

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

Ardından, test dosyanızdaki değeri aşağıdaki gibi taklit edebilirsiniz:

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

İşlevlerinizi içe aktarma

İşlevlerinizi içe aktarmak için require kullanarak ana işlevler dosyanızı modül olarak içe aktarın. Bu işlemi yalnızca firebase-functions-test başlatıldıktan ve yapılandırma değerleri taklit edildikten sonra yaptığınızdan emin olun.

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

firebase-functions-test öğesini çevrimdışı modda başlattıysanız ve işlevler kodunuzda admin.initializeApp() varsa işlevlerinizi içe aktarmadan önce bunu saplamanız gerekir:

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

Arka plan (HTTP olmayan) işlevlerini test etme

HTTP dışı işlevleri test etme süreci aşağıdaki adımları içerir:

  1. Test etmek istediğiniz işlevi test.wrap yöntemiyle sarmalayın.
  2. Test verileri oluşturma
  3. Sarmalanmış işlevi, oluşturduğunuz test verileri ve belirtmek istediğiniz tüm etkinlik bağlamı alanlarıyla birlikte çağırın.
  4. Davranışla ilgili iddialarda bulunma

Öncelikle test etmek istediğiniz işlevi sarmalayın. functions/index.js içinde makeUppercase adlı bir işleviniz olduğunu ve bunu test etmek istediğinizi varsayalım. Aşağıdakileri functions/test/index.test.js dilinde yazın

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

wrapped, çağrıldığında makeUppercase işlevini çağıran bir işlevdir. wrapped 2 parametre alır:

  1. data (gerekli): makeUppercase adresine gönderilecek veriler. Bu, doğrudan yazdığınız işlev işleyicisine gönderilen ilk parametreye karşılık gelir. firebase-functions-test, özel veriler veya örnek veriler oluşturmak için yöntemler sunar.
  2. eventContextOptions (isteğe bağlı): Belirtmek istediğiniz etkinlik bağlamı alanları. Etkinlik bağlamı, yazdığınız işlev işleyicisine gönderilen ikinci parametredir. eventContextOptions wrapped çağrılırken parametre eklenmezse anlamlı alanlarla bir etkinlik bağlamı oluşturulmaya devam eder. Oluşturulan alanlardan bazılarını burada belirterek geçersiz kılabilirsiniz. Yalnızca geçersiz kılmak istediğiniz alanları eklemeniz gerektiğini unutmayın. Geçersiz kılmadığınız tüm alanlar oluşturulur.
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
});

Test verileri oluşturma

Sarmalanmış bir işlevin ilk parametresi, temel işlevi çağırmak için kullanılan test verileridir. Test verileri oluşturmanın çeşitli yolları vardır.

Özel verileri kullanma

firebase-functions-test, işlevlerinizi test etmek için gereken verileri oluşturmaya yönelik çeşitli işlevler içerir. Örneğin, test.firestore.makeDocumentSnapshot kullanarak Firestore DocumentSnapshot oluşturun. İlk bağımsız değişken verilerdir, ikinci bağımsız değişken tam referans yoludur ve belirtebileceğiniz anlık görüntünün diğer özellikleri için isteğe bağlı üçüncü bir bağımsız değişken vardır.

// 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 veya onWrite işlevini test ediyorsanız iki anlık görüntü oluşturmanız gerekir: biri önceki durum, diğeri sonraki durum için. Ardından, bu anlık görüntülerle makeChange yöntemini kullanarak Change nesnesi oluşturabilirsiniz.

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

Diğer tüm veri türleri için benzer işlevleri API referansında bulabilirsiniz.

Örnek verileri kullanma

Testlerinizde kullanılan verileri özelleştirmeniz gerekmiyorsa firebase-functions-test, her işlev türü için örnek veriler oluşturma yöntemleri sunar.

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

Her işlev türü için örnek verileri alma yöntemleri hakkında bilgi edinmek için API referansı bölümüne bakın.

Stubbed verileri kullanma (çevrimdışı mod için)

SDK'yı çevrimdışı modda başlattıysanız ve Cloud Firestore veya Realtime Database işlevini test ediyorsanız gerçek bir DocumentSnapshot veya DataSnapshot oluşturmak yerine saplamalar içeren düz bir nesne kullanmanız gerekir.

Aşağıdaki işlev için bir birim testi yazdığınızı varsayalım:

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

İşlevin içinde snap iki kez kullanılır:

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

Test kodunda, bu kod yollarının her ikisinin de çalışacağı düz bir nesne oluşturun ve yöntemleri saplamak için Sinon'u kullanın.

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

İddialarda bulunma

SDK'yı başlattıktan, işlevleri sarmaladıktan ve verileri oluşturduktan sonra, oluşturulan verilerle sarmalanmış işlevleri çağırabilir ve davranışla ilgili onaylamalar yapabilirsiniz. Bu onaylamaları yapmak için Chai gibi bir kitaplık kullanabilirsiniz.

Online modda onaylama yapma

Firebase Test SDK için Cloud Functions'yi çevrimiçi modda başlattıysanız firebase-admin SDK'sını kullanarak istenen işlemlerin (ör. veritabanına yazma) gerçekleştiğini onaylayabilirsiniz.

Aşağıdaki örnekte, "INPUT"un test projesinin veritabanına yazıldığı onaylanıyor.

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

Çevrimdışı modda iddialarda bulunma

İşlevin beklenen dönüş değeriyle ilgili onaylamalar yapabilirsiniz:

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

Belirli yöntemlerin çağrıldığını ve beklediğiniz parametrelerle çağrıldığını onaylamak için Sinon spies'ı da kullanabilirsiniz.

HTTP işlevlerini test etme

HTTP onCall işlevlerini test etmek için arka plan işlevlerini test etme ile aynı yaklaşımı kullanın.

onRequest HTTP işlevlerini test ediyorsanız aşağıdaki durumlarda firebase-functions-test kullanmanız gerekir:

  • functions.config() kullanıyorsanız
  • İşleviniz bir Firebase projesiyle veya diğer Google API'leriyle etkileşim kuruyor ve testleriniz için gerçek bir Firebase projesi ile kimlik bilgilerini kullanmak istiyorsunuz.

Bir HTTP onRequest işlevi iki parametre alır: istek nesnesi ve yanıt nesnesi. addMessage() örnek işlevini şu şekilde test edebilirsiniz:

  • sendMessage() işlevi çağırdığı için yanıt nesnesindeki yönlendirme işlevini geçersiz kılın.
  • Yönlendirme işlevi içinde, yönlendirme işlevinin hangi parametrelerle çağrılması gerektiğiyle ilgili onaylamalar yapmaya yardımcı olması için chai.assert işlevini kullanın:
// 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);

Test temizliği

Test kodunuzun en sonunda temizleme işlevini çağırın. Bu işlem, SDK başlatıldığında ayarlanan ortam değişkenlerinin ayarını kaldırır ve SDK'yı kullanarak gerçek zamanlı veritabanı DataSnapshot veya Firestore DocumentSnapshot oluşturduysanız oluşturulmuş olabilecek Firebase uygulamalarını siler.

test.cleanup();

Eksiksiz örnekleri inceleyin ve daha fazla bilgi edinin

Tam örnekleri Firebase GitHub deposunda inceleyebilirsiniz.

Daha fazla bilgi için firebase-functions-test API referansına bakın.