Veja nesta página as práticas recomendadas e as ferramentas para escrever testes de unidade para suas
funções, como testes que poderiam fazer parte de um sistema de integração contínua
(CI). Para facilitar o teste, o Firebase fornece o Firebase Test SDK para Cloud Functions. Ele
é distribuído no npm como firebase-functions-test
e é um SDK de teste complementar
para firebase-functions
. O Firebase Test SDK para Cloud Functions:
- Cuida da configuração e da eliminação apropriada para seus testes, como
a definição e desconfiguração das variáveis de ambiente necessárias para
firebase-functions
. - Gera dados de amostra e contextos de evento para que seja necessário especificar apenas os campos relevantes ao teste.
Como configurar o teste
Instale o firebase-functions-test
e Mocha, um
framework de teste, executando os seguintes comandos na pasta de funções:
npm install --save-dev firebase-functions-test
npm install --save-dev mocha
Em seguida, crie uma pasta test
dentro da pasta de funções, crie um novo arquivo
dentro dele para o código de teste e dê a ele um nome semelhante a index.test.js
.
Por fim, modifique functions/package.json
para adicionar o seguinte:
"scripts": {
"test": "mocha --reporter spec"
}
Depois de escrever os testes, é possível executá-los executando npm test
dentro do diretório de funções.
Inicialização do Firebase Test SDK para Cloud Functions
Há duas maneiras de usar firebase-functions-test
:
- Modo on-line (recomendado): escreva testes que interajam com um projeto do Firebase dedicado a testes para que gravações de banco de dados, criação de usuários e ações similares realmente aconteçam e seu código de teste possa inspecionar os resultados. Isso também significa que outros SDKs do Google usados nas suas funções também funcionarão.
- Modo off-line: escreva testes de unidade off-line e em silos sem efeitos colaterais. Isso significa que todas as chamadas de método que interagem com um produto do Firebase (por exemplo, uma gravação no banco de dados ou a criação de um usuário) precisam ser fragmentadas. Em geral, não é recomendado usar o modo off-line no caso das funções do Cloud Firestore ou do Realtime Database porque isso aumenta muito a complexidade do código de teste.
Inicializar o SDK no modo on-line (recomendado)
Para criar testes que interajam com um projeto de teste, envie
os valores de configuração exigidos para inicializar o app usando
firebase-admin
. Também é preciso enviar o caminho para um arquivo de chave da conta de serviço.
Para acessar os valores de configuração do seu projeto do Firebase, siga as etapas a seguir:
- Abra as configurações do projeto no console do Firebase.
- Em Apps, selecione o app desejado.
No painel à direita, selecione a opção para fazer o download de um arquivo de configuração para apps Apple e Android.
Em apps da Web, selecione Configuração para exibir os valores de configuração.
Para criar um arquivo de chave, siga as etapas a seguir:
- Abra o painel Contas de serviço do console do Google Cloud.
- Selecione a conta de serviço padrão do App Engine e use o menu de opções à direita para selecionar Criar chave.
- Selecione JSON para a opção do tipo de chave e clique em Criar.
Depois de salvar o arquivo de chave, inicialize o 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');
Como inicializar o SDK no modo off-line
Se quiser escrever testes completamente off-line, você poderá inicializar o SDK sem nenhum parâmetro:
// At the top of test/index.test.js
const test = require('firebase-functions-test')();
Como simular valores de configuração
Se você usar functions.config()
no código de funções, poderá simular os valores
de configuração. Por exemplo, se functions/index.js
contiver o seguinte código:
const functions = require('firebase-functions/v1');
const key = functions.config().stripe.key;
Você poderá simular o valor que está no arquivo de teste da seguinte forma:
// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});
Como importar suas funções
Para importar suas funções, use require
para importar o arquivo de funções principais como um
módulo. Certifique-se de fazer isso somente depois de inicializar firebase-functions-test
e simular os valores de configuração.
// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code
Se você inicializou firebase-functions-test
no
modo off-line e tem
admin.initializeApp()
no seu código de funções, faça um stub para ele antes de
importar suas funções:
// 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');
Como testar funções em segundo plano (não HTTP)
Para testar funções que não são HTTP, siga estas etapas:
- Encapsule a função que você gostaria de testar com o método
test.wrap
. - Construa dados de teste para a função.
- Chame a função encapsulada com os dados de teste que você construiu e qualquer campo de contexto de evento que você gostaria de especificar.
- Faça declarações sobre o comportamento.
Encapsule a função que gostaria de testar. Digamos que você tenha uma função em functions/index.js
chamada makeUppercase
que gostaria de testar. Escreva o seguinte código no functions/test/index.test.js
// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);
wrapped
é uma função que invoca makeUppercase
quando é chamada. wrapped
requer dois parâmetros:
- data (obrigatório): os dados a serem enviados a
makeUppercase
. Isso corresponde diretamente ao primeiro parâmetro enviado ao gerenciador de funções que você escreveu.firebase-functions-test
fornece métodos para construir dados personalizados ou dados de exemplo. - eventContextOptions (opcional): campos do contexto do evento que você gostaria de especificar. O contexto do evento é o segundo parâmetro enviado ao administrador de funções que você escreveu. Se você não incluir um parâmetro
eventContextOptions
ao chamarwrapped
, um contexto do evento ainda será gerado com campos relevantes. Você pode modificar alguns dos campos gerados, especificando-os aqui. Observe que você só precisa incluir os campos que gostaria de modificar. Todos os campos que você não modificou são gerados.
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
});
Como criar dados de teste
O primeiro parâmetro de uma função encapsulada é o conjunto de dados de teste para chamar a função subjacente. Existem várias maneiras de criar dados de teste.
Com usar dados personalizados
firebase-functions-test
tem diversas funções para construir dados necessários para testar suas funções. Por exemplo, use test.firestore.makeDocumentSnapshot
para criar um Firestore DocumentSnapshot
. O primeiro argumento é o conjunto de dados, e o segundo é o caminho de referência completo. Há um terceiro argumento opcional para outras propriedades do snapshot que você pode especificar.
// 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);
Se você estiver testando uma função onUpdate
ou onWrite
, será necessário criar dois snapshots: um para o estado anterior e outro para o após. Em seguida, é possível usar o método makeChange
para criar um objeto Change
com esses snapshots.
// 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);
Consulte a referência da API para mais informações sobre funções parecidas de todos os outros tipos de dados.
Como usar dados de exemplo
Se você não precisar personalizar os dados usados nos seus testes,
utilize firebase-functions-test
para gerar dados de exemplo para cada
tipo de função.
// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();
Consulte os métodos da referência da API e veja dados de exemplo para cada tipo de função.
Como usar dados fragmentados (modo off-line)
Se você inicializou o SDK no modo off-line e está testando uma função do Cloud Firestore ou do
Realtime Database, deve usar um objeto simples com stubs
em vez de criar um DocumentSnapshot
ou DataSnapshot
real.
Vamos supor que você esteja escrevendo um teste de unidade para a seguinte função:
// 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); });
Dentro da função, snap
é usado duas vezes:
snap.val()
snap.ref.parent.child('uppercase').set(uppercase)
No código de teste, crie um objeto simples em que ambos os caminhos de código funcionem e use o Sinon para fragmentar os métodos.
// 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);
Como fazer declarações
Depois de inicializar o SDK, encapsular as funções e criar dados, é possível chamar as funções encapsuladas com os dados construídos e fazer declarações sobre o comportamento. Você pode usar uma biblioteca como a Chai para fazer isso.
Como fazer declarações no modo on-line
Se você inicializou o Firebase Test SDK para Cloud Functions no modo on-line, é possível
declarar que as ações desejadas (como uma gravação em banco de dados) ocorreram usando o
SDK firebase-admin
.
O exemplo abaixo declara que o valor INPUT foi gravado no banco de dados do projeto de teste.
// 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'); }); });
Como fazer declarações no modo off-line
Você pode fazer declarações sobre o valor de retorno esperado da função:
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);
Você também pode usar espiões de teste do Sinon para declarar que alguns métodos foram chamados e que os parâmetros corretos foram usados.
Como testar funções HTTP
Para testar funções HTTP onCall, use a mesma abordagem de teste de funções em segundo plano.
Caso você esteja testando as funções HTTP onRequest, use o método firebase-functions-test
se:
- você usar
functions.config()
; - sua função interage com um projeto do Firebase ou com outras APIs do Google e você gostaria de usar um projeto real do Firebase e suas credenciais para os testes.
Uma função HTTP onRequest usa dois parâmetros: um objeto de solicitação e um objeto de resposta. Veja como você pode testar o exemplo de função addMessage()
:
- Substitua a função de redirecionamento no objeto de resposta, já que
sendMessage()
a chama. - Na função de redirecionamento, use chai.assert para fazer declarações sobre quais parâmetros devem ser usados para chamar a função de redirecionamento:
// 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);
Limpeza de testes
No final do seu código de teste, chame a função de limpeza. Isso restaura as variáveis de ambiente que o SDK definiu quando foi inicializado e exclui os aplicativos do Firebase que podem ter sido criados se você usou o SDK para criar um banco de dados em tempo real DataSnapshot
ou Firestore DocumentSnapshot
.
test.cleanup();
Confira os exemplos completos e saiba mais
Você pode conferir os exemplos completos no repositório do Firebase no GitHub.
- Como testar funções de Realtime Database e HTTP no modo on-line
- Como testar funções de Realtime Database e HTTP no modo off-line
Para saber mais, consulte a Referência da API para firebase-functions-test
.