Bulut İşlevlerinin birim testi

Bu sayfada, Sürekli Entegrasyon (CI) sisteminin bir parçası olacak testler gibi, işlevleriniz için birim testleri yazmaya yönelik en iyi uygulamalar ve araçlar açıklanmaktadır. Firebase, testi kolaylaştırmak amacıyla Bulut İşlevleri için Firebase Test SDK'sını sağlar. Npm'de firebase-functions-test olarak dağıtılır ve firebase-functions eşlik eden bir test SDK'sıdır. Bulut İşlevleri için Firebase Test SDK'sı:

  • firebase-functions ihtiyaç duyduğu ortam değişkenlerini ayarlama ve ayarlama gibi testleriniz için uygun kurulum ve sökme işlemleriyle ilgilenir.
  • Örnek veriler ve olay bağlamı oluşturur, böylece yalnızca testinizle ilgili alanları belirtmeniz gerekir.

Test kurulumu

Function 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

Daha sonra, işlevler klasörünün içinde bir test klasörü oluşturun, içinde test kodunuz için yeni bir dosya oluşturun ve onu index.test.js gibi bir şekilde adlandırın.

Son olarak, aşağıdakileri eklemek için functions/package.json dosyasını değiştirin:

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

Testleri yazdıktan sonra, bunları işlevler dizininizde npm test çalıştırarak çalıştırabilirsiniz.

Cloud Functions için Firebase Test SDK'sının başlatılması

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

  1. Çevrimiçi mod (önerilir): Veritabanı yazma, kullanıcı oluşturma vb. işlemlerin gerçekten gerçekleşmesi ve test kodunuzun sonuçları inceleyebilmesi için test etmeye adanmış bir Firebase projesiyle etkileşim kuran testler yazın. Bu aynı zamanda işlevlerinizde kullanılan diğer Google SDK'larının da çalışacağı anlamına gelir.
  2. Çevrimdışı mod: Hiçbir yan etki olmadan silolanmış ve çevrimdışı birim testleri yazın. Bu, bir Firebase ürünüyle etkileşime giren tüm yöntem çağrılarının (örneğin, veritabanına yazma veya kullanıcı oluşturma) saplanması gerektiği anlamına gelir. Cloud Firestore veya Realtime Database işlevlerine sahipseniz, test kodunuzun karmaşıklığını büyük ölçüde artıracağından, çevrimdışı modun kullanılması genellikle önerilmez.

SDK'yı çevrimiçi modda başlatın (önerilir)

Bir test projesiyle etkileşim kuran testler yazmak istiyorsanız, uygulamayı firebase-admin aracılığıyla başlatmak için gereken proje yapılandırma değerlerini ve 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'da istediğiniz uygulamayı seçin.
  3. Sağ bölmede, Apple ve Android uygulamaları için bir yapılandırma dosyası indirme seçeneğini seçin.

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

Bir 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 öğesini seçin.
  3. İstendiğinde anahtar türü olarak JSON'u seçin ve Oluştur'a tıklayın.

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

// 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'yı çevrimdışı modda başlat

Tamamen çevrimdışı testler yazmak istiyorsanız 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ğerleri ile alay etme

İşlev kodunuzdafunction.config functions.config() işlevini kullanırsanız, yapılandırma değerleriyle dalga geçebilirsiniz. Örneğin, functions/index.js aşağıdaki kodu içeriyorsa:

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

Daha sonra test dosyanızın içindeki değerle şu şekilde alay edebilirsiniz:

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

İşlevlerinizi içe aktarma

İşlevlerinizi içe aktarmak için, ana işlevler dosyanızı modül olarak içe aktarma require kullanın. Bunu yalnızca firebase-functions-test başlatıldıktan ve yapılandırma değerleriyle alay ettikten 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 çevrimdışı modda başlattıysanız ve işlev kodunuzda admin.initializeApp() varsa, işlevlerinizi içe aktarmadan önce onu 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 sarın
  2. Test verilerini oluşturma
  3. Sarılmış işlevi, oluşturduğunuz test verileriyle ve belirtmek istediğiniz etkinlik bağlam alanlarıyla birlikte çağırın.
  4. Davranış hakkında iddialarda bulunun.

İlk önce test etmek istediğiniz işlevi sarın. functions/index.js dosyasında makeUppercase adında test etmek istediğiniz bir fonksiyonunuz olduğunu varsayalım. Aşağıdakileri functions/test/index.test.js dosyasına 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 çağıran bir işlevdir. wrapped 2 parametre alır:

  1. veri (gerekli): makeUppercase 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 sağlar.
  2. eventContextOptions (isteğe bağlı): belirtmek istediğiniz etkinlik bağlamının alanları. Olay bağlamı, yazdığınız işlev işleyicisine gönderilen ikinci parametredir. wrapped öğesini çağırırken bir eventContextOptions parametresi eklemezseniz, yine de mantıklı alanlarla bir olay bağlamı oluşturulur. 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 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 verilerinin oluşturulması

Sarılmış bir fonksiyonun ilk parametresi, temeldeki fonksiyonun çağrılacağı test verileridir. Test verilerini 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 bir dizi işleve sahiptir. Örneğin, bir Firestore DocumentSnapshot oluşturmak için test.firestore.makeDocumentSnapshot kullanın. İlk bağımsız değişken veridir ve ikinci bağımsız değişken tam referans yoludur ve anlık görüntünün diğer özellikleri için belirtebileceğiniz isteğe bağlı bir üçüncü 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);

Bir onUpdate veya onWrite işlevini test ediyorsanız iki anlık görüntü oluşturmanız gerekir: biri önceki durum için, diğeri sonraki durum için. Daha sonra bu anlık görüntülerle bir Change nesnesi oluşturmak için makeChange yöntemini kullanabilirsiniz.

// 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ürlerine yönelik benzer işlevler için API referansına bakın.

Örnek verileri kullanma

Testlerinizde kullanılan verileri özelleştirmeniz gerekmiyorsa firebase-functions-test , her işlev türü için örnek veriler oluşturmaya yönelik yöntemler 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üne yönelik örnek verileri almaya yönelik yöntemler için API referansına bakın.

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

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

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

Fonksiyonun 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 bulunmak

SDK'yı başlattıktan, işlevleri sarmaladıktan ve verileri oluşturduktan sonra, sarılmış işlevleri oluşturulan verilerle çağırabilir ve davranış hakkında iddialarda bulunabilirsiniz. Bu iddiaları yapmak için Chai gibi bir kütüphaneyi kullanabilirsiniz.

Çevrimiçi modda iddialarda bulunma

Cloud Functions için Firebase Test SDK'sını çevrimiçi modda başlattıysanız, firebase-admin SDK'yı kullanarak istenen eylemlerin (veritabanına yazma gibi) gerçekleştiğini iddia edebilirsiniz.

Aşağıdaki örnekte test projesinin veritabanına 'INPUT' yazıldığı belirtilmektedir.

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

Fonksiyonun beklenen dönüş değeri hakkında iddialarda bulunabilirsiniz:

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

Belirli yöntemlerin ve beklediğiniz parametrelerle çağrıldığını iddia etmek için Sinon casuslarını da kullanabilirsiniz.

HTTP işlevlerini test etme

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

HTTP onRequest işlevlerini test ediyorsanız aşağıdaki durumlarda firebase-functions-test kullanmalısınız:

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

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

  • sendMessage() onu çağırdığından, 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ği konusunda iddialarda bulunmaya yardımcı olması için chai.assert'i 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);

Temizleme testi

Test kodunuzun en sonunda temizleme işlevini çağırın. Bu, SDK'nın başlatıldığında ayarladığı ortam değişkenlerini kaldırır ve SDK'yı gerçek zamanlı bir veritabanı DataSnapshot veya Firestore DocumentSnapshot oluşturmak için kullandıysanız oluşturulmuş olabilecek Firebase uygulamalarını siler.

test.cleanup();

Örneklerin tamamını inceleyin ve daha fazlasını öğrenin

Örneklerin tamamını Firebase GitHub deposunda inceleyebilirsiniz.

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