Testowanie jednostkowe funkcji w Cloud Functions

Na tej stronie znajdziesz sprawdzone metody i narzędzia do pisania testów jednostkowych funkcji takich jak testy, które wchodzą w skład ciągłej integracji (CI) systemu. Aby ułatwić testowanie, Firebase udostępnia pakiet SDK Firebase Test dla Cloud Functions. it jest rozpowszechniany w npm jako firebase-functions-test i jest towarzyszącym testowym pakietem SDK do: firebase-functions. Pakiet SDK Firebase Test SDK dla Cloud Functions:

  • Dba o odpowiednią konfigurację i sekcję testów na potrzeby testów, np. i ustawić zmienne środowiskowe wymagane przez firebase-functions.
  • Generuje przykładowe dane i kontekst zdarzenia, więc wystarczy określić pola, które są istotne w Twoim teście.
.

Konfiguracja testowa

Zainstaluj zarówno firebase-functions-test, jak i Mocha, platformę testowania, uruchamiając w folderze funkcji te polecenia:

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

Następnie utwórz folder test w folderze funkcji, a następnie utwórz nowy plik. wpisz w nim kod testowy i nadaj mu nazwę podobną do index.test.js.

Na koniec zmodyfikuj functions/package.json, by dodać te elementy:

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

Po napisaniu testów możesz je uruchomić, uruchamiając w nich npm test w katalogu funkcji.

Inicjowanie pakietu SDK Firebase Test dla Cloud Functions

Z usługi firebase-functions-test można korzystać na 2 sposoby:

  1. Tryb online (zalecany): pisz testy, które współdziałają z projektem Firebase. przeznaczone do testowania, tak aby zapisy w bazie danych, tworzenie przez użytkownika itd. i można sprawdzić ich wyniki za pomocą kodu testowego. Oznacza to również, że pozostałe Będą one również działać z pakietami SDK Google używanymi w Twoich funkcjach.
  2. Tryb offline: możesz tworzyć silosowane i offline testy jednostkowe bez efektów ubocznych. Oznacza to, że wszystkie wywołania metod, które wchodzą w interakcję z usługą Firebase (np. do zapisu w bazie danych lub tworzenia użytkownika) należy skrócić. Korzystanie w trybie offline tryb zwykle nie jest zalecany, jeśli korzystasz z Cloud Firestore lub Bazy danych czasu rzeczywistego ponieważ znacznie zwiększa to złożoność kodu testowego.

Zainicjuj pakiet SDK w trybie online (zalecane)

Jeśli chcesz pisać testy współdziałające z projektem testowym, musisz: podać wartości konfiguracji projektu wymagane do zainicjowania aplikacji firebase-admin oraz ścieżka do pliku klucza konta usługi.

Aby pobrać wartości konfiguracyjne projektu Firebase:

  1. Otwórz ustawienia projektu w Konsola Firebase.
  2. W sekcji Twoje aplikacje wybierz odpowiednią aplikację.
  3. W panelu po prawej stronie wybierz opcję pobrania pliku konfiguracji dla aplikacji Apple i Androida.

    W przypadku aplikacji internetowych wybierz opcję Konfiguracja, aby wyświetlić wartości konfiguracyjnych.

Aby utworzyć plik klucza:

  1. Otwórz panel Konta usługi. w konsoli Google Cloud.
  2. Wybierz domyślne konto usługi App Engine i użyj menu opcji na stronie prawym przyciskiem myszy i wybierz Utwórz klucz.
  3. Gdy pojawi się prośba, wybierz typ klucza JSON i kliknij Utwórz.

Po zapisaniu pliku klucza zainicjuj pakiet 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');

Zainicjuj pakiet SDK w trybie offline

Jeśli chcesz pisać testy offline, możesz zainicjować pakiet SDK bez żadnych parametrów:

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

Pozorowanie wartości konfiguracji

Jeśli używasz functions.config() w kodzie funkcji, możesz symulować konfigurację . Jeśli na przykład functions/index.js zawiera ten kod:

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

Następnie możesz symulować wartość w pliku testowym w ten sposób:

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

Importowanie funkcji

Aby zaimportować funkcje, użyj narzędzia require, aby zaimportować główny plik funkcji jako plik . Pamiętaj, aby robić to dopiero po zainicjowaniu usługi firebase-functions-test, i naśladowania wartości konfiguracji.

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

Jeśli zainicjowano firebase-functions-test w trybie offline oraz admin.initializeApp() w kodzie funkcji, musisz go użyć przed importowanie funkcji:

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

Testowanie funkcji w tle (innych niż HTTP)

Proces testowania funkcji innych niż HTTP obejmuje te kroki:

  1. Zapakuj funkcję, którą chcesz przetestować za pomocą metody test.wrap
  2. Tworzenie danych testowych
  3. Wywołaj funkcję opakowaną z użyciem utworzonych przez Ciebie danych testowych i dowolnego zdarzenia pola kontekstu, które chcesz określić.
  4. Wyrażaj sobie spostrzeżenia na temat zachowań.

Najpierw zapakuj funkcję, którą chcesz przetestować. Załóżmy, że masz funkcję w functions/index.js o nazwie makeUppercase, którą chcesz przetestować. Wpisz obserwowane w grupie functions/test/index.test.js

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

wrapped to funkcja, która wywołuje funkcję makeUppercase po jej wywołaniu. wrapped przyjmuje 2 parametry:

  1. data (wymagane): dane do wysłania do usługi makeUppercase. To bezpośrednio odpowiada pierwszemu parametrowi wysłanemu do modułu obsługi funkcji, który co napisał. firebase-functions-test udostępnia metody tworzenia niestandardowych lub przykładowe dane.
  2. eventContextOptions (opcjonalnie): pola kontekstu zdarzenia, do których określić. Kontekst zdarzenia to drugi parametr wysyłany do funkcji modułu obsługi funkcji. Jeśli nie podasz atrybutu eventContextOptions podczas wywoływania funkcji wrapped kontekst zdarzenia jest generowany z odpowiednimi polami. Niektóre wygenerowane pola możesz zastąpić, i je określić. Pamiętaj, że musisz uwzględnić tylko te pola, które które chcesz zastąpić. Wszystkie pola, których nie zastąpiono, zostaną wygenerowane.
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
});

Tworzenie danych testowych

Pierwszym parametrem funkcji opakowanej są dane testowe do wywołania bazowego z funkcją. Dane testowe można tworzyć na kilka sposobów.

Korzystanie z danych niestandardowych

firebase-functions-test udostępnia szereg funkcji do tworzenia danych potrzebnych aby przetestować funkcje. Przykład: test.firestore.makeDocumentSnapshot aby utworzyć DocumentSnapshot Firestore. Pierwszy argument to dane, a argument drugi argument to pełna ścieżka odniesienia i jest opcjonalny trzeci argument .

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

Jeśli testujesz funkcję onUpdate lub onWrite, musisz utworzyć ze stanem „przed” i „po” ze stanem „po”. Następnie może użyć metody makeChange, aby utworzyć obiekt Change z tymi zrzutami.

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

zapoznaj się z dokumentacją interfejsu API, aby uzyskać informacje o podobnych funkcjach. dla pozostałych typów danych.

Korzystanie z przykładowych danych

Jeśli nie musisz dostosowywać danych używanych podczas testów: firebase-functions-test oferuje metody generowania przykładowych danych dla poszczególnych typ funkcji.

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

w dokumentacji interfejsu API znajdziesz metody pobierz przykładowe dane dla każdego typu funkcji.

Używanie skróconych danych (w trybie offline)

Jeśli pakiet SDK został zainicjowany w trybie offline i testujesz Cloud Firestore lub Bazy danych czasu rzeczywistego, użyj zwykłego obiektu z krotkami zamiast tworzenia rzeczywistego DocumentSnapshot lub DataSnapshot.

Załóżmy, że piszesz test jednostkowy dla następującej funkcji:

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

W obrębie funkcji pole snap jest używane dwukrotnie:

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

Utwórz w kodzie testowym zwykły obiekt, w którym będą działać obie te ścieżki kodu. i użyj polecenia Sinon, aby utworzyć atrapę metod.

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

Składanie asercji

Po zainicjowaniu pakietu SDK, opakowaniu funkcji i stworzeniu danych może wywoływać opakowane funkcje z utworzonymi danymi i wykonywać asercje na temat zachowania użytkowników. Na przykład biblioteki Chai możesz używać do ustanowienie tych założeń.

Tworzenie asercji w trybie online

Jeśli zainicjujesz pakiet SDK Firebase Test dla Cloud Functions w trybie online, może określić, że pożądane działania (np. zapis w bazie danych) zostały wykonane przez za pomocą pakietu SDK firebase-admin.

W poniższym przykładzie potwierdza się, że „INPUT” została zapisana w bazie danych projektu testowego.

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

Tworzenie asercji w trybie offline

Możesz składać asercje dotyczące oczekiwanej wartości zwracanej przez funkcję:

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

Możesz też użyć szpiegów Sinona, aby: zapewnić, że zostały wywołane określone metody z oczekiwanymi parametrami.

Testowanie funkcji HTTP

Aby przetestować funkcje HTTP onCall, użyj tej samej metody co do testowania funkcji w tle.

Jeśli testujesz funkcje HTTP onRequest, użyj firebase-functions-test, jeśli:

  • Używasz functions.config()
  • Twoja funkcja współdziała z projektem Firebase lub innymi interfejsami API Google, chcesz używać w testach prawdziwego projektu Firebase i jego danych logowania.

Funkcja HTTP onRequest ma 2 parametry: obiekt żądania i odpowiedź obiektu. Oto jak możesz przetestować przykładową funkcję addMessage():

  • Zastąp funkcję przekierowania w obiekcie odpowiedzi, ponieważ sendMessage() właśnie na to.
  • W ramach funkcji przekierowania użyj polecenia chai.assert. aby ułatwić sobie określenie, jakie parametry powinna wykonywać funkcja przekierowująca być wywołana przy użyciu:
// 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);

Czyszczenie testowe

Na samym końcu kodu testowego wywołaj funkcję czyszczenia. Wyłącza to ustawienie zmiennych środowiskowych ustawionych przez pakiet SDK podczas inicjowania, aplikacje Firebase, które mogły zostać utworzone, jeśli użyto pakietu SDK do utworzenia prawdziwego DataSnapshot lub Firestore DocumentSnapshot.

test.cleanup();

Zapoznaj się z pełnymi przykładami i dowiedz się więcej

Pełne przykłady znajdziesz w repozytorium Firebase na GitHubie.

Więcej informacji znajdziesz w dokumentacji interfejsu API. za firebase-functions-test.