Confira tudo que foi anunciado no Firebase Summit e veja como usar o Firebase para acelerar o desenvolvimento de apps e executar os aplicativos com confiança. Saiba mais

Teste de unidade do Cloud Functions

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Esta página descreve as melhores práticas e ferramentas para escrever testes de unidade para suas funções, como testes que fariam parte de um sistema de Integração Contínua (CI). Para facilitar os testes, o Firebase fornece o Firebase Test SDK para Cloud Functions. Ele é distribuído no npm como firebase firebase-functions-test e é um SDK de teste complementar para firebase-functions . O SDK de teste do Firebase para Cloud Functions:

  • Cuida da configuração e desmontagem apropriadas para seus testes, como configuração e desativação de variáveis ​​de ambiente necessárias para firebase-functions .
  • Gera dados de amostra e contexto de eventos, para que você precise especificar apenas os campos relevantes para o seu teste.

Configuração de teste

Instale o firebase-functions-test e o Mocha , uma estrutura 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 de test dentro da pasta de funções, crie um novo arquivo dentro dela para seu código de teste e nomeie-o como index.test.js .

Finalmente, modifique functions/package.json para adicionar o seguinte:

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

Depois de escrever os testes, você pode executá-los executando npm test dentro do diretório de funções.

Inicializando o Firebase Test SDK para Cloud Functions

Existem duas maneiras de usar firebase-functions-test :

  1. Modo online (recomendado): escreva testes que interajam com um projeto do Firebase dedicado a testes para que as gravações do banco de dados, as criações do usuário etc. realmente aconteçam e seu código de teste possa inspecionar os resultados. Isso também significa que outros SDKs do Google usados ​​em suas funções também funcionarão.
  2. Modo offline: Escreva testes de unidade isolados e offline sem efeitos colaterais. Isso significa que todas as chamadas de método que interagem com um produto Firebase (por exemplo, gravar no banco de dados ou criar um usuário) precisam ser fragmentadas. Usar o modo offline geralmente não é recomendado se você tiver as funções Cloud Firestore ou Realtime Database, pois aumenta muito a complexidade do seu código de teste.

Inicializar o SDK no modo online (recomendado)

Se quiser escrever testes que interajam com um projeto de teste, você precisa fornecer os valores de configuração do projeto necessários para inicializar o aplicativo por meio firebase-admin e o caminho para um arquivo de chave da conta de serviço.

Para obter os valores de configuração do seu projeto Firebase:

  1. Abra as configurações do seu projeto no console do Firebase .
  2. Em Seus aplicativos, selecione o aplicativo desejado.
  3. No painel direito, selecione a opção para baixar um arquivo de configuração para aplicativos Apple e Android.

    Para aplicativos da web, selecione Config para exibir os valores de configuração.

Para criar um arquivo de chave:

  1. Abra o painel Contas de serviço do Console do Google Cloud.
  2. Selecione a conta de serviço padrão do App Engine e use o menu de opções à direita para selecionar Criar chave .
  3. Quando solicitado, selecione JSON para o 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');

Inicializar o SDK no modo offline

Se você quiser escrever testes completamente offline, pode inicializar o SDK sem nenhum parâmetro:

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

Zombando de valores de configuração

Se você usar functions.config() em seu código de funções, poderá zombar dos valores de configuração. Por exemplo, se functions/index.js contiver o seguinte código:

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

Então você pode simular o valor dentro do seu arquivo de teste da seguinte forma:

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

Importando suas funções

Para importar suas funções, use require para importar seu arquivo de funções principais como um módulo. Certifique-se de fazer isso apenas após inicializar firebase-functions-test e simular 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 offline e tem admin.initializeApp() em seu código de funções, então você precisa fazer um stub 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');

Testando funções em segundo plano (não HTTP)

O processo para testar funções não HTTP envolve as seguintes etapas:

  1. Envolva a função que você gostaria de testar com o método test.wrap
  2. Construir dados de teste
  3. Invoque a função agrupada com os dados de teste que você construiu e quaisquer campos de contexto de evento que gostaria de especificar.
  4. Faça afirmações sobre o comportamento.

Primeiro envolva a função que você gostaria de testar. Digamos que você tenha uma função em functions/index.js chamada makeUppercase , que gostaria de testar. Escreva o seguinte em functions/test/index.test.js

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

makeUppercase wrapped é chamada. wrapped leva 2 parâmetros:

  1. data (obrigatório): os dados a serem enviados para makeUppercase . Isso corresponde diretamente ao primeiro parâmetro enviado ao manipulador de função que você escreveu. firebase-functions-test fornece métodos para construir dados personalizados ou dados de exemplo.
  2. eventContextOptions (opcional): campos do contexto do evento que você gostaria de especificar. O contexto do evento é o segundo parâmetro enviado ao manipulador de função que você escreveu. Se você não incluir um parâmetro eventContextOptions ao chamar wrapped , um contexto de evento ainda será gerado com campos sensatos. Você pode substituir alguns dos campos gerados especificando-os aqui. Observe que você só precisa incluir os campos que deseja substituir. Todos os campos que você não substituiu 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
});

Construindo dados de teste

O primeiro parâmetro de uma função agrupada são os dados de teste com os quais invocar a função subjacente. Existem várias maneiras de construir dados de teste.

Usando dados personalizados

firebase-functions-test tem várias funções para construir os dados necessários para testar suas funções. Por exemplo, use test.firestore.makeDocumentSnapshot para criar um Firestore DocumentSnapshot . O primeiro argumento são os dados e o segundo argumento é o caminho de referência completo, e há um terceiro argumento opcional para outras propriedades do instantâneo 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 , precisará criar dois instantâneos: um para o estado anterior e outro para o estado posterior. Em seguida, você pode usar o método makeChange para criar um objeto Change com esses instantâneos.

// 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 obter funções semelhantes para todos os outros tipos de dados.

Usando dados de exemplo

Se você não precisa personalizar os dados usados ​​em seus testes, o firebase-functions-test oferece métodos 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 a referência da API para obter métodos para obter dados de exemplo para cada tipo de função.

Usando dados fragmentados (para o modo offline)

Se você inicializou o SDK no modo off-line e está testando uma função Cloud Firestore ou Realtime Database, use um objeto simples com stubs em vez de criar um DocumentSnapshot ou DataSnapshot real.

Digamos 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 onde ambos os caminhos de código funcionarão e use o Sinon para fazer o stub dos 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);

Fazendo afirmações

Depois de inicializar o SDK, agrupar as funções e construir dados, você pode invocar as funções agrupadas com os dados construídos e fazer afirmações sobre o comportamento. Você pode usar uma biblioteca como Chai para fazer essas afirmações.

Fazendo asserções no modo online

Se você inicializou o Firebase Test SDK para Cloud Functions no modo online , pode afirmar que as ações desejadas (como uma gravação de banco de dados) ocorreram usando o firebase-admin SDK.

O exemplo abaixo afirma que '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');
  });
});

Fazendo asserções no modo offline

Você pode fazer afirmaçõ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 do Sinon para afirmar que certos métodos foram chamados e com os parâmetros que você espera.

Testando funções HTTP

Para testar as funções HTTP onCall, use a mesma abordagem do teste de funções em segundo plano .

Se você estiver testando funções HTTP onRequest, deverá usar firebase-functions-test se:

  • Você usa functions.config()
  • Sua função interage com um projeto do Firebase ou outras APIs do Google, e você gostaria de usar um projeto real do Firebase e suas credenciais para seus testes.

Uma função HTTP onRequest recebe dois parâmetros: um objeto de solicitação e um objeto de resposta. Aqui está como você pode testar a função de exemplo addMessage() :

  • Substitua a função de redirecionamento no objeto de resposta, pois sendMessage() a chama.
  • Dentro da função de redirecionamento, use chai.assert para ajudar a fazer afirmações sobre quais parâmetros a função de redirecionamento deve ser chamada:
// 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 teste

No final do seu código de teste, chame a função de limpeza. Isso desativa as variáveis ​​de ambiente que o SDK definiu quando foi inicializado e exclui 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();

Revise exemplos completos e saiba mais

Você pode revisar os exemplos completos no repositório GitHub do Firebase.

Para saber mais, consulte a referência da API para firebase-functions-test .