Cloud Functions'ın birim testi

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

  • Testleriniz için uygun kurulumu ve ayrıştırmayı (ör. firebase-functions için gereken ortam değişkenlerini ayarlamak ve ayarlarını kaldırmak) gerçekleştirir.
  • Örnek veriler ve etkinlik bağlamı oluşturur. Böylece yalnızca testinizle alakalı alanları belirtmeniz gerekir.

Test kurulumu

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

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

Daha sonra, işlevler klasöründe bir test klasörü oluşturun, test kodunuz için bu dosyanın içinde yeni bir dosya oluşturun ve klasöre index.test.js gibi bir ad verin.

Son olarak, aşağıdakini eklemek için functions/package.json öğesini değiştirin:

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

Testleri yazdıktan sonra işlev dizininizde npm test komutunu çalıştırarak çalıştırabilirsiniz.

Cloud Functions için Firebase Test SDK'yı başlatma

firebase-functions-test iki şekilde kullanılabilir:

  1. Online mod (önerilir): Teste özel bir Firebase projesiyle etkileşime giren testler yazın. Böylece veritabanı yazma, kullanıcı oluşturma vb. işlemleri gerçekleştirir ve test kodunuzun sonuçları inceleyebilir. Bu durum, işlevlerinizde kullanılan diğer Google SDK'larının da çalışacağı anlamına gelir.
  2. Çevrimdışı mod: Yan etki olmadan, ayrı ayrı ve çevrimdışı birim testleri yazın. Bu, bir Firebase ürünüyle etkileşim kuran (ör. veritabanına yazma veya kullanıcı oluşturma) tüm yöntem çağrılarının stub dosyalanması gerektiği anlamına gelir. Test kodunuzun karmaşıklığını büyük ölçüde artırdığından, Cloud Firestore veya Realtime Database işlevleriniz varsa çevrimdışı modu kullanmanız genellikle önerilmez.

SDK'yı online modda başlat (ö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 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 Console'un 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 seçin.
  3. İstendiğinde anahtar türü için JSON'u seçin ve Create'i (Oluştur) 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 isterseniz SDK'yı herhangi bir parametre olmadan başlatabilirsiniz:

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

Sahte yapılandırma değerleri

İşlevler kodunuzda functions.config() kullanıyorsanız yapılandırma değerleriyle deneme yapabilirsiniz. Örneğin, functions/index.js aşağıdaki kodu içeriyorsa:

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

Ardından, test dosyanızın içindeki değeri aşağıdaki şekilde oluşturabilirsiniz:

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

İşlevlerinizi içe aktarma

İşlevlerinizi içe aktarmak için require komutunu kullanarak ana işlev 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ğerlerini taklit 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 öğesini çevrimdışı modda başlattıysanız ve işlev kodunuzda admin.initializeApp() varsa işlevlerinizi içe aktarmadan önce 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 olmayan 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 verilerini oluşturma
  3. Sarmalanmış işlevi, oluşturduğunuz test verileriyle ve belirtmek istediğiniz tüm etkinlik bağlamı alanlarıyla çağırın.
  4. Davranış hakkında iddialarda bulunmak.

Önce test etmek istediğiniz işlevi sarmalayın. functions/index.js içinde, test etmek istediğiniz makeUppercase adlı bir işleviniz olduğunu 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 yöntemini çağıran bir işlevdir. wrapped, 2 parametre alır:

  1. data (gerekli): makeUppercase ürününe gönderilecek veriler. Bu, doğrudan, işlev işleyiciye 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şleyiciye gönderilen ikinci parametredir. wrapped çağrılırken eventContextOptions parametresi eklemezseniz geçerli alanlarla bir etkinlik içeriği yine de 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 verilerini oluşturma

Sarmalanmış işlevin ilk parametresi, temel işlevin çağrılacağı test verileridir. Test verilerini oluşturmanın birkaç yolu vardır.

Özel verileri kullanma

firebase-functions-test, işlevlerinizi test etmek amacıyla gereken verileri oluşturmak için çeşitli işlevlere sahiptir. Örneğin, Firestore DocumentSnapshot oluşturmak için test.firestore.makeDocumentSnapshot kullanın. İlk bağımsız değişken veri, ikinci bağımsız değişken ise tam referans yoludur. Ayrıca anlık görüntünün diğer özellikleri için belirtebileceğiniz isteğe bağlı üçüncü bir bağımsız değişken bulunur.

// 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 biri öncesi ve diğeri sonrası durumu için olmak üzere iki anlık görüntü oluşturmanız gerekir. Ardından, bu anlık görüntülerle 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ürleriyle ilgili 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 veri 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üyle ilgili örnek verileri alma yöntemleri için API referansına bakın.

Stdubed veri 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 saplama içeren düz bir nesne kullanmanız gerekir.

Aşağıdaki fonksiyon 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 değiştirmek 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);

Onaylama

SDK'yı başlattıktan, işlevleri sarmaladıktan ve verileri oluşturduktan sonra, oluşturulan verilerle sarmalanmış işlevleri çağırabilir ve davranış hakkında onay alabilirsiniz. Bu iddiaları oluşturmak için Chai gibi bir kitaplık kullanabilirsiniz.

Online modda onaylamalar yapma

Cloud Functions için Firebase Test SDK'sını online modda başlattıysanız firebase-admin SDK'sını kullanarak istenen işlemlerin (veri tabanına yazma gibi) gerçekleştirildiğini onaylayabilirsiniz.

Aşağıdaki örnek, test projesinin veritabanına "INPUT" öğesinin yazıldığını doğrulamaktadır.

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

İşlevin beklenen dönüş değeri hakkında onay alabilirsiniz:

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 çağrıldığını 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 etme ile aynı yaklaşımı kullanın.

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

  • functions.config() kullanıyorsunuz
  • İşleviniz bir Firebase projesi veya diğer Google API'leriyle etkileşim kurduğunda testlerinizde gerçek bir Firebase projesi ve kimlik bilgilerini kullanmak istersiniz.

HTTP onRequest işlevi, iki parametre alır: istek nesnesi ve yanıt nesnesi. addMessage() örnek işlevini nasıl test edebileceğiniz aşağıda açıklanmıştır:

  • sendMessage() tarafından çağrıldığından yanıt nesnesindeki yönlendirme işlevini geçersiz kılın.
  • Yönlendirme işlevinde, yönlendirme işlevinin hangi parametrelerle çağrılacağı konusunda onaylamalar yapmak için chai.assert 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, SDK'nın ilk kullanıma hazırlanırken ayarladığı ortam değişkenlerini ayarlamaz 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();

Tüm örnekleri inceleyin ve daha fazla bilgi edinin

Tüm örnekleri Firebase GitHub deposunda inceleyebilirsiniz.

Daha fazla bilgi için firebase-functions-test ile ilgili API referansını inceleyin.