Na tej stronie opisano sprawdzone metody i narzędzia do pisania testów jednostkowych funkcji, np. testów, które byłyby częścią systemu ciągłej integracji (CI). Aby ułatwić testowanie, Firebase udostępnia Firebase Test SDK dla Cloud Functions. Jest on rozpowszechniany w npm jako firebase-functions-test
i stanowi towarzyszący pakiet SDK do testowania pakietu firebase-functions
. Firebase Test SDK dla Cloud Functions:
- Zajmuje się odpowiednią konfiguracją i demontażem testów, np. ustawianiem i usuwaniem zmiennych środowiskowych potrzebnych przez
firebase-functions
. - Generuje przykładowe dane i kontekst zdarzenia, więc musisz tylko określić tylko te pola, które są istotne dla Twojego testu.
Konfiguracja testowa
Zainstaluj firebase-functions-test
i Mocha, framework testowy, uruchamiając te polecenia w folderze funkcji:
npm install --save-dev firebase-functions-test
npm install --save-dev mocha
Następnie w folderze functions utwórz folder test
, a w nim nowy plik na kod testowy i nadaj mu nazwę, np. 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ć, wykonując npm test
w katalogu funkcji.
Inicjuję Firebase Test SDK dla Cloud Functions
Z usługi firebase-functions-test
można korzystać na 2 sposoby:
- Tryb online (zalecany): napisz testy, które będą wchodzić w interakcję z projektem Firebase przeznaczonym do testowania, aby zapisy w bazie danych, tworzenie użytkowników itp. rzeczywiście miały miejsce, a kod testowy mógł sprawdzić wyniki. Oznacza to też, że inne pakiety Google SDK używane w Twoich funkcjach również będą działać.
- Tryb offline: pisanie testów jednostkowych w trybie offline bez efektów ubocznych. Oznacza to, że wszystkie wywołania metod, które współpracują z usługą Firebase (np. zapisywanie w bazie danych lub tworzenie użytkownika), muszą być zastąpione przez stuby. Korzystanie z trybu offline nie jest zalecane, jeśli masz funkcje Cloud Firestore lub Realtime Database, ponieważ znacznie zwiększa to złożoność kodu testowego.
Zainicjuj pakiet SDK w trybie online (zalecane)
Jeśli chcesz pisać testy, które będą wchodzić w interakcję z projektem testowym, musisz podać wartości konfiguracji projektu, które są potrzebne do zainicjowania aplikacji za pomocą funkcji firebase-admin
, oraz ścieżkę do pliku klucza konta usługi.
Aby uzyskać wartości konfiguracji projektu Firebase:
- Otwórz ustawienia projektu w konsoli Firebase.
- W sekcji Twoje aplikacje wybierz odpowiednią aplikację.
W panelu po prawej stronie wybierz opcję pobierania pliku konfiguracyjnego dla aplikacji na urządzenia z systemem Android i Apple.
W przypadku aplikacji internetowych wybierz Konfiguracja, aby wyświetlić wartości konfiguracji.
Aby utworzyć plik klucza:
- Otwórz panel Konta usługi w konsoli Google Cloud.
- Wybierz App Engine domyślne konto usługi, a następnie w menu opcji po prawej stronie kliknij Utwórz klucz.
- 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')();
Symulowanie wartości konfiguracji
Jeśli w kodzie funkcji używasz funkcji functions.config()
, możesz zasymulować wartości konfiguracji. Jeśli na przykład functions/index.js
zawiera ten kod:
const functions = require('firebase-functions/v1');
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 funkcji require
, aby zaimportować plik główny funkcji jako moduł. Zrób to dopiero po zainicjowaniu firebase-functions-test
i ustawieniu wartości konfiguracji.
// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code
Jeśli funkcja firebase-functions-test
została zainicjowana w trybie offline, a w kodzie funkcji występuje funkcja admin.initializeApp()
, musisz utworzyć jej zastępnik przed zaimportowaniem 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 działających w tle (nie HTTP)
Proces testowania funkcji innych niż HTTP obejmuje te kroki:
- Otocz funkcję, którą chcesz przetestować, metodą
test.wrap
- Tworzenie danych testowych
- Wywołaj opakowaną funkcję z utworzonymi przez siebie danymi testowymi i dowolnymi polami kontekstu zdarzenia, które chcesz określić.
- robić założenia dotyczące zachowania;
Najpierw zapakuj funkcję, którą chcesz przetestować. Załóżmy, że masz w functions/index.js
funkcję o nazwie makeUppercase
, którą chcesz przetestować. Napisać w języku 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
. wrapped
przyjmuje 2 parametry:
- data (wymagane): dane do wysłania do
makeUppercase
. Jest on bezpośrednio powiązany z pierwszym parametrem wysłanym do napisanego przez Ciebie modułu funkcji.firebase-functions-test
udostępnia metody tworzenia danych niestandardowych lub przykładowych. - eventContextOptions (opcjonalnie): pola kontekstu zdarzenia, które chcesz określić. Kontekst zdarzenia to drugi parametr wysyłany do napisanego przez Ciebie modułu obsługi funkcji. Jeśli nie podasz parametru
eventContextOptions
podczas wywołania funkcjiwrapped
, kontekst zdarzenia zostanie nadal wygenerowany z odpowiednimi polami. Możesz zastąpić niektóre wygenerowane pola, podając je tutaj. Pamiętaj, że musisz uwzględnić tylko pola, które chcesz zastąpić. Generowane są wszystkie pola, których nie zastąpiono.
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 funkcji bazowej. Dane testowe można tworzyć na kilka sposobów.
Korzystanie z danych niestandardowych
firebase-functions-test
zawiera wiele funkcji do tworzenia danych potrzebnych do testowania funkcji. Aby na przykład utworzyć instancję DocumentSnapshot
Firestore, użyj polecenia test.firestore.makeDocumentSnapshot
. Pierwszy argument to dane, a drugi to pełna ścieżka referencyjna. Opcjonalny trzeci argument służy do określania innych właściwości migawki.
// 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ć 2 migawki: jeden dla stanu sprzed i jeden dla stanu po. Następnie możesz użyć metody makeChange
, aby utworzyć obiekt Change
z tymi migawkami.
// 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);
Więcej informacji o podobnych funkcjach dla wszystkich innych typów danych znajdziesz w dokumentacji interfejsu API.
Korzystanie z przykładowych danych
Jeśli nie musisz dostosowywać danych używanych w testach, firebase-functions-test
udostępnia metody generowania przykładowych danych dla każdego typu 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 pobierania przykładowych danych dla poszczególnych typów funkcji.
Używanie skróconych danych (w trybie offline)
Jeśli inicjujesz pakiet SDK w trybie offline i testujesz funkcję Cloud Firestore lub Realtime Database, użyj zwykłego obiektu z zastępnikami zamiast tworzyć rzeczywiste obiekty 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); });
Wewnątrz funkcji snap
jest używany 2 razy:
snap.val()
snap.ref.parent.child('uppercase').set(uppercase)
W kodzie testowym utwórz zwykły obiekt, w którym będą działać obie ścieżki kodu, a do testowania metod użyj Sinon.
// 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);
Tworzenie stwierdzeń
Po zainicjowaniu pakietu SDK, owinięciu funkcji i utworzeniu danych możesz wywoływać owinięte funkcje z utworzonymi danymi i wyraźnie określać ich działanie. Do tworzenia tych stwierdzeń możesz użyć biblioteki takiej jak Chai.
Tworzenie stwierdzeń w trybie online
Jeśli Firebase Test SDK dla Cloud Functions został zainicjowany w trybie online, możesz za pomocą pakietu SDK firebase-admin
sprawdzić, czy zostały wykonane żądane działania (np. zapisanie danych w bazie danych).
W przykładzie poniżej widać, że pole „INPUT” zostało zapisane 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 stwierdzeń 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żywać podsłuchiwania Sinon, aby sprawdzić, czy zostały wywołane określone metody i czy zostały one wywołane z oczekiwanymi parametrami.
Testowanie funkcji HTTP
Aby przetestować funkcje onCall HTTP, zastosuj takie samo podejście jak w przypadku testowania funkcji działających w tle.
Jeśli testujesz funkcje HTTP onRequest, użyj opcji firebase-functions-test
, jeśli:
- Używasz
functions.config()
- Twoja funkcja wchodzi w interakcję z projektem Firebase lub innymi interfejsami API Google i chcesz w testach użyć prawdziwego projektu Firebase oraz jego danych logowania.
Funkcja HTTP onRequest przyjmuje 2 parametry: obiekt żądania i obiekt odpowiedzi. Oto jak możesz przetestować przykładową funkcję addMessage()
:
- Zastąp funkcję przekierowania w obiekcie odpowiedzi, ponieważ
sendMessage()
wywołuje ją. - W funkcji przekierowania użyj funkcji chai.assert, aby sformułować twierdzenia dotyczące parametrów, z którymi należy wywoływać funkcję przekierowania:
// 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);
Testowanie porządkowania
Na samym końcu kodu testowego wywołaj funkcję czyszczenia. Powoduje to usunięcie zmiennych środowiskowych ustawionych przez pakiet SDK podczas inicjowania i usuwa aplikacje Firebase, które mogły zostać utworzone, jeśli pakiet SDK służy do tworzenia bazy danych DataSnapshot
w czasie rzeczywistym lub Firestore DocumentSnapshot
.
test.cleanup();
Zapoznaj się z pełnymi przykładami i dowiedz się więcej
Pełne przykłady znajdziesz w repozytorium GitHub Firebase.
- Testowanie funkcji Realtime Database i funkcji HTTP w trybie online
- Testowanie funkcji Realtime Database i HTTP w trybie offline
Więcej informacji znajdziesz w dokumentacji API dotyczącej firebase-functions-test
.