Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

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 oferece o SDK de teste do Firebase para o 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 definir e desdefinir variáveis ​​de ambiente necessárias para firebase-functions .
  • Gera dados de amostra e contexto de evento, para que você só precise especificar os campos relevantes para 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 .

Por fim, 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 SDK de teste do Firebase para Cloud Functions

Existem duas maneiras de usar firebase-functions-test :

  1. Modo online (recomendado): escreva testes que interagem com um projeto do Firebase dedicado a testes para que as gravações de banco de dados, criações de usuários 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 em silos e offline sem efeitos colaterais. Isso significa que qualquer chamada de método que interage com um produto Firebase (por exemplo, gravar no banco de dados ou criar um usuário) precisa ser stub. O uso do modo off-line geralmente não é recomendado se você tiver funções do Cloud Firestore ou do Realtime Database, pois aumenta muito a complexidade do seu código de teste.

Inicialize o SDK no modo online (recomendado)

Se você quiser escrever testes que interagem com um projeto de teste, precisará 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 de conta de serviço.

Para obter os valores de configuração do seu projeto do 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 de 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');

Inicialize o SDK no modo offline

Se você deseja 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')();

Valores de configuração simulados

Se você usar functions.config() em seu 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');
const key = functions.config().stripe.key;

Então você pode simular o valor dentro do seu arquivo de teste assim:

// 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 principal 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 o firebase-functions-test no modo off -line e tem admin.initializeApp() em seu código de funções, você precisa 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. Enrole a função que você gostaria de testar com o método test.wrap
  2. Construir dados de teste
  3. Invoque a função encapsulada com os dados de teste que você construiu e quaisquer campos de contexto de evento que você 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ê deseja 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 sensíveis. 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 encapsulada são os dados de teste para 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 estiver testando uma função onUpdate ou onWrite , você 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 funções semelhantes para todos os outros tipos de dados.

Usando dados de exemplo

Se você não precisar 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 métodos para obter dados de exemplo para cada tipo de função.

Usando dados em stub (para modo offline)

Se você inicializou o SDK no modo off-line e está testando uma função do Cloud Firestore ou do 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 esses dois caminhos de código funcionarão e use Sinon para stub 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);

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 declarações sobre o comportamento. Você pode usar uma biblioteca como Chai para fazer essas afirmações.

Fazendo afirmações no modo online

Se você inicializou o SDK de teste do Firebase para Cloud Functions no modo on -line , poderá confirmar que as ações desejadas (como uma gravação no banco de dados) ocorreram usando o SDK do firebase-admin .

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 os espiões da Sinon para afirmar que certos métodos foram chamados e com os parâmetros que você espera.

Testando funções HTTP

Para testar funções HTTP onCall, use a mesma abordagem que testar funções em segundo plano .

Se você estiver testando funções HTTP onRequest, use 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 asserçõ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 os aplicativos do Firebase que podem ter sido criados se você usou o SDK para criar um DataSnapshot de banco de dados em tempo real 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 .