Cloud Functions birim testi

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

  • firebase-functions için gereken ortam değişkenlerini ayarlama ve kaldırma gibi testleriniz için uygun kurulum ve kaldırma işlemlerini 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 test çerçevesi olan Jest'i yükleyin:

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

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

Son olarak, functions/package.json dosyasını değiştirerek aşağıdakileri ekleyin:

"scripts": {
  "test": "jest"
}

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

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

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

  1. Online mod (önerilir): Test için ayrılmış bir Firebase projesiyle etkileşim kuran testler yazın. Böylece veritabanı yazma, kullanıcı oluşturma vb. işlemler gerçekten gerçekleşir ve test kodunuz sonuçları inceleyebilir. Bu, işlevlerinizde kullanılan diğer Google SDK'larının da çalışacağı anlamına gelir.
  2. Çevrimdışı mod: Yan etkisi olmayan, yalıtılmış ve çevrimdışı birim testleri yazın. Bu, bir Firebase ürünüyle etkileşim kuran tüm yöntem çağrılarının (ör. veritabanına yazma veya kullanıcı oluşturma) taslak olarak oluşturulması gerektiği anlamına gelir. Cloud Firestore veya Realtime Database işlevleriniz varsa çevrimdışı modu kullanmanız genellikle önerilmez. Bu mod, test kodunuzun karmaşıklığını önemli ölçüde artırır.

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

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

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

  1. Firebase konsolunda Ayarlar > Genel sayfasına gidin.

  2. Uygulamalarınız kartına gidin ve istediğiniz uygulamayı seçin.

  3. Firebase yapılandırmanızı alın:

    • Apple ve Android uygulamaları için yapılandırma dosyası indirme seçeneğini belirleyin.

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

Anahtar dosyası oluşturmak için:

  1. Google Cloud konsolunda Hizmet Hesapları bölmesine gidin.

  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ü olarak 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
// 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 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ğ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 şu şekilde taklit edebilirsiniz:

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

İşlevleriniz içe aktarılıyor

İş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 yapın.

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

firebase-functions-test'i çevrimdışı modda başlattıysanız ve işlev kodunuzda admin.initializeApp() varsa işlevlerinizi içe aktarmadan önce bunu sarmalamanız gerekir. Test dosyanızda firebase-admin öğesini admin olarak isteyin, ardından initializeApp() öğesini bir Jest taklidiyle değiştirin:

// If initializeApp() is called in index.js, we mock it out before requiring index.js
adminInitStub = jest.spyOn(admin, 'initializeApp').mockImplementation(() => {});
// 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ımlardan oluşur:

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

Öncelikle 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 işlevini çağıran bir işlevdir. wrapped 2 parametre alır:

  1. data (zorunlu): makeUppercase adresine gönderilecek veriler. Bu, yazdığınız işlev işleyiciye gönderilen ilk parametreye doğrudan 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ı alanları. Etkinlik bağlamı, yazdığınız işlev işleyiciye gönderilen ikinci parametredir. wrapped yöntemini çağırırken eventContextOptions parametresi eklemezseniz etkinlik bağlamı yine de anlamlı alanlarla 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 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ılacak test verileridir. Test verileri oluşturmanın birkaç yolu vardır.

Özel verileri kullanma

firebase-functions-test, işlevlerinizi test etmek için gereken verileri oluşturmaya yönelik ç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 veriler, ikinci bağımsız değişken tam referans yoludur. 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);

Bir onUpdate veya onWrite işlevini test ediyorsanız iki anlık görüntü oluşturmanız gerekir: biri önceki durum, diğeri ise sonraki durum için. Ardından, 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ü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 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ü için örnek veri alma yöntemlerini API referansında bulabilirsiniz.

Sınırlı veriler kullanılıyor (ç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 taslakları olan basit 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ğı basit bir nesne oluşturun ve yöntemleri saptamak için Jest sahte işlevlerini kullanın.

// The following lines create 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,
    }
  }
};

İddialarda bulunma

SDK'yı başlattıktan, işlevleri sarmaladıktan ve verileri oluşturduktan sonra, sarmalanmış işlevleri oluşturulan verilerle çağırabilir ve davranışla ilgili iddialarda bulunabilirsiniz. Bu iddialarda bulunmak için Jest'in expect özelliğini kullanabilirsiniz.

Online modda iddialarda bulunma

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

Aşağıdaki örnekte, test projesinin veritabanına "INPUT" yazıldığı iddia edilmektedir.

// 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.
  return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => {
    // Assert that the value is the uppercased version of our input.
    expect(createdSnap.val()).toBe('INPUT');
  });
});

Çevrimdışı modda iddiada bulunma

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

const childParam = 'uppercase';
const setParam = 'INPUT';
// Spies/mocks 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 setStub = jest.fn().mockImplementation((val) => val === setParam ? true : undefined);
const childStub = jest.fn().mockImplementation((path) => path === childParam ? { set: setStub } : undefined);
// The following lines create 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,
    }
  }
};
// Wrap the makeUppercase function.
const wrapped = test.wrap(myFunctions.makeUppercase);
// Since we've mocked 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 => {
  expect(makeUppercaseResult).toBe(true);
});

Belirli yöntemlerin çağrıldığını ve beklediğiniz parametrelerle çağrıldığını doğrulamak için Jest taklit işlevlerini de 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 kuruyorsa ve testleriniz için gerçek bir Firebase projesi ile kimlik bilgilerini kullanmak istiyorsanız.

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

  • addMessage(), Realtime Database'e aktardığı için admin.database() sarmalayıcısı. Çevrimdışı modda initializeApp() için taslak oluşturduğunuzdan Admin SDK'nın kullanabileceği bir Firebase uygulaması yoktur ve gerçek admin.database(), The default Firebase app does not exist ile başarısız olur:

    const pushStub = jest.fn().mockResolvedValue({ ref: 'new_ref' });
    const refStub = jest.fn().mockReturnValue({ push: pushStub });
    Object.defineProperty(admin, 'database', {
      configurable: true,
      writable: true,
      value: jest.fn().mockReturnValue({ ref: refStub }),
    });
    
  • addMessage() tarafından çağrıldığı için yanıt nesnesindeki yönlendirme işlevini geçersiz kılın.

  • Yönlendirme işlevinde, yönlendirme işlevinin hangi parametrelerle çağrılması gerektiğiyle ilgili iddialarda bulunmak için Jest'in expect 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) => {
    expect(code).toBe(303);
    expect(url).toBe('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'nın başlatıldığında ayarladığı ortam değişkenlerini 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

Eksiksiz örneklerin tamamını Firebase GitHub deposunda inceleyebilirsiniz.

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