Ir para o console

Teste de unidade do Cloud Functions

Esta página descreve 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, na sigla em inglês). Para facilitar esse processo, o Firebase fornece o SDK de teste do Firebase para o Cloud Functions. Ele é distribuído no NPM como firebase-functions-test e é um SDK de teste complementar para firebase-functions. O SDK de teste do Firebase para o Cloud Functions:

  • cuida da configuração e desmontagem apropriada dos seus testes, como a configuração e a desativação de variáveis de ambiente exigidas por firebase-functions;
  • gera dados de amostra e contexto de eventos para que você precise especificar apenas os campos relevantes ao teste.

Como configurar o teste

Instale o firebase-functions-test e o Mocha, uma estrutura de teste. Para fazer isso, basta executar os seguintes comandos na sua pasta de funções:

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

Após a instalação, crie uma pasta test dentro da pasta de funções. Em seguida, crie um novo arquivo dentro dela para seu código de teste e nomeie-o como index.test.js.

Por fim, modifique o arquivo functions/package.json para adicionar o seguinte:

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

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

Como inicializar o SDK de teste do Firebase para o Cloud Functions

Veja as duas maneiras de usar o firebase-functions-test:

  1. Modo off-line: escreva testes de unidade off-line e em silos sem efeitos colaterais. Isso significa que é necessário fragmentar qualquer chamada de método que interaja com um produto do Firebase, como uma gravação no banco de dados ou a criação de um usuário.
  2. Modo on-line: escreva testes que interajam com um projeto do Firebase dedicado a testes para que gravações de banco de dados, criações de usuários, etc. realmente aconteçam, e seu código de teste possa analisar os resultados. Isso também significa que outros SDKs do Google usados ​​em suas funções também funcionarão.

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 inicializar o SDK no modo on-line

Se quiser escrever testes que interajam com um projeto de teste, você precisará fornecer os valores de configuração do projeto necessários para inicializar o app por meio do firebase-admin e 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:

  1. Acesse o Firebase console.
  2. Selecione seu projeto, clique em Adicionar o Firebase ao seu aplicativo da Web. Os valores de configuração serão exibidos em um pop-up.

Para criar um arquivo de chave, siga as etapas a seguir:

  1. Abra o painel de contas de serviço do Google Cloud Console.
  2. Selecione a conta de serviço padrão do App Engine e use o menu de opções à direita para selecionar a opção Criar chave.
  3. 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 simular valores de configuração

Os valores de configuração poderão ser simulados se você usar functions.config() em seu código de funções. Por exemplo, se functions/index.js tiver o seguinte código:

const functions = require('firebase-functions');
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

Use require para importar seu arquivo de funções principais como um módulo. Faça isso somente depois de inicializar firebase-functions-test e de 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 o firebase-functions-test no modo off-line e tiver admin.initializeApp() no seu código de funções, será necessário fragmentá-lo 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:

  1. Una a função que você gostaria de testar com o método test.wrap.
  2. Construa dados de teste para a função.
  3. Chame a função unida com os dados de teste que você construiu e qualquer campo de contexto de evento que você gostaria de especificar.
  4. Faça declarações sobre o comportamento.

Una a função que gostaria de testar. Vamos supor que você tenha uma função no functions/index.js chamada makeUppercase que você 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 chama makeUppercase. wrapped apresenta dois parâmetros:

  1. 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. O firebase-functions-test fornece métodos para criar 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 administrador de funções que você escreveu. Se você não incluir um parâmetro eventContextOptions ao chamar a função wrapped, um contexto de evento ainda será gerado com campos sensíveis. 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 unida é 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

O firebase-functions-test apresenta vários recursos para a criação de dados necessárias para testar suas funções. Por exemplo, use test.firestore.makeDocumentSnapshot para criar um DocumentSnapshot do Firestore. 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 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, será necessário criar dois instantâneos: um para o estado anterior e outro para o estado posterior. Em seguida, você poderá 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 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, o firebase-functions-test oferecerá 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 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 Firestore ou do Database, pode ser mais fácil usar um objeto simples com fragmentos em vez de criar um DocumentSnapshot ou DataSnapshot real e depois fragmentar seus métodos.

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();
      console.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, unir as funções e criar dados, é possível chamar as funções unidas 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 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 fazer declarações no modo on-line

Se inicializou o SDK de teste do Firebase para o Cloud Functions no modo on-line, você pode 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 em tempo real.

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

Testar funções HTTP

Caso você esteja testando as funções HTTP onRequest, use o método firebase-functions-test se:

  • você usa o 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 a função de exemplo addMessage():

  • Modifique a função de redirecionamento no objeto de resposta, já que sendMessage() chama essa função.
  • 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. Ela define as variáveis ​​de ambiente que o SDK definiu na inicialização e exclui os apps do Firebase que podem ter sido criados se você usou o SDK para desenvolver um banco de dados em tempo real do DataSnapshot ou DocumentSnapshot do Firestore.

test.cleanup();

Confira os exemplos completos e saiba mais

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

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