Criando um mock de data com Jest

front-end

tags:

  • javascript
  • jest

Postado em 08/06/2020

Olá! Neste post vou mostrar como fazer um mock usando Jest. Esse mock servirá para "congelar" a data atual, evitando que testes de funções que usem a data atual através do método Date.now comecem a dar erro com o passar do tempo.

Acho legal destacar que, neste post, não vou abordar temas mais teóricos sobre testes e nem sobre a utilização/configuração do Jest. Vou focar somente no mock proposto.

Entendendo o que é um mock

Antes de começar a falar sobre como fazer, quero falar um pouco sobre o que é um mock. Primeiramente, vou deixar essa definição que achei bem bacana na página da Wikipedia:

Objetos Mock, objetos simulados ou simplesmente Mock (do inglês Mock object) em desenvolvimento de software são objetos que simulam o comportamento de objetos reais de forma controlada. São normalmente criados para testar o comportamento de outros objetos. Em outras palavras, os objetos mock são objetos “falsos” que simulam o comportamento de uma classe ou objeto “real” para que possamos focar o teste na unidade a ser testada.

Ou seja, criar um mock é uma técnica usada para simular algum recurso, mas de forma que a gente consiga ter previsibilidade no comportamento. Ás vezes os mocks também são usados para facilitar o teste e evitar conexões com ambientes externos, o que pode ser lento e complexo.

Algumas situações onde um mock é bastante útil:

  • simular um banco de dados, evitando a necessidade de se criar uma instância de um banco toda vez que o teste rodar.

  • simular a resposta de alguma API externa, evitando requisições desnecessárias para essa API e também evitando que algum erro externo ao teste faça o mesmo falhar.

  • simular algum estado específico do sistema, como por exemplo o momento atual.

Explicando o problema

Vamos imaginar um ambiente de uma loja online. Dentro da página de um determinado pedido pendente existe uma informação que mostra há quantos dias o pedido foi feito. Para saber essa informação precisamos de outras duas informações: qual o dia que o pedido foi realizado e que dia é hoje. Subtraindo as duas datas conseguimos obter o valor necessário.

Para facilitar nosso exemplo, vamo usar uma implementação bastante simples, a ideia é pegar o time stamp das duas datas e subtrair. O resultado será dividido pela quantidade de milisegundos que existem em um dia:

milissegundosEmUmDia = (1000 * 60 * 60 * 24) //86.400.000
(dataAtual - dataDoPedido) / milissegundosEmUmDia

Implementando a função

Uma implementação possível seria (para facilitar, vamos considerar que o parâmetro já chegue como timestamp):

function pendingDays(initialDate) {
  return Math.trunc((Date.now() - initialDate) / (1000 _ 60 _ 60 \* 24));
}

Considerando que hoje seja dia 08 de junho de 2020, se usarmos essa função passando como parâmetro a data do dia 03 de junho de 2020, devemos receber 5 como resposta:

const initialDate = new Date("06/03/2020")
pendingDays(initialDate.getTime()) // returns 5

Testando a função

Como falei no começo do post, não vou focar na parte de configuração do Jest, vamos apenas focar no caso de teste específico dessa função:


describe("pendingDays", () => {
  it("returns the number of days pending", () => {
    const initialDate = new Date("06/03/2020");
    const pendingDaysNumber = pendingDays(initialDate.getTime());

    expect(pendingDaysNumber).toEqual(5);
});

Depois de rodar todos os testes e tudo passar certinho, podemos seguir com nosso fluxo de desenvolvimento até mandar o código pra produção e considerar a tarefa concluída.

Porém no dia seguinte, esse teste começa a falhar, mesmo a implementação da função estando totalmente correta. É aqui que percebemos que nosso teste tinha um grande problema: ele testa uma resposta estática para uma função que é dinâmica. Precisamos garantir que essa função vai retornar sempre o resultado esperado.

Alterando o comportamento do Date.now

Como pudemos perceber, nossa função está correta e o que precisamos alterar é a forma como escrevemos nosso teste. Nossa função utiliza o Date.now, que podemos considerar que sempre irá retornar um valor diferente quando for chamado.

Para resolver isso, precisamos fazer com que o funcionamento do Date.now seja previsível, por isso vamos fazer um mock para ele.

A ideia é: vamos sobrescrever o método Date.now dentro do ambiente do Jest, para que ele retorne sempre o valor que a gente definir. Dessa forma podemos fazer com que sempre seja retornado o timestamp do dia 08 de junho de 2020.

Para fazer isso, vamos adicionar uma linha em nosso teste:


describe("pendingDays", () => {
  it("returns the number of days pending", () => {
    global.Date.now = jest.fn(() => new Date('06/08/2020').getTime());

    const initialDate = new Date("06/03/2020");
    const pendingDaysNumber = pendingDays(initialDate.getTime());

    expect(pendingDaysNumber).toEqual(5);

})

Impedindo efeitos colaterais

A forma como implementamos nosso mock resolve o problema para o teste que estava quebrando, mas como alteramos a variável global que é usada por todos os outros testes, isso pode fazer com que eles comecem a quebrar por causa da nossa alteração.

Para resolver isso, temos que fazer com que depois do nosso teste o Date.now volte ao normal. Por isso vamos adicionar um salvar o valor original do global.Date antes de alterá-lo e fazer com que essa variável seja restaurada antes de cada teste:

describe("pendingDays", () => {

  const originalDate = global.Date;

  beforeEach(() => {
    global.Date = originalDate;
  });

  it("returns the number of days pending", () => {
    global.Date.now = jest.fn(() => new Date('06/08/2020').getTime());

    const initialDate = new Date("06/03/2020");
    const pendingDaysNumber = pendingDays(initialDate.getTime());

    expect(pendingDaysNumber).toEqual(5);
});

Conclusão

Ás vezes nosso código depende de variáveis externas ou muito dinâmicas, o que torna muito difícil de conseguirmos escrever testes eficientes para ele. Para esse tipo de situação, geralmente um mock deve ajudar a tornar esses comportamentos previsíveis e dessa forma testarmos se nosso código responde da maneira esperada.

É importante lembrar que é necessário desfazermos nosso mock após usá-lo para garantir que ele não vai atrapalhar outros testes que não são relacionados com o problema que estávamos resolvendo inicialmente.

Compartilhe:

posts relacionados

Falando um pouco sobre Arrow Functions

Arrow function possui uma sintaxe mais curta, além de não fazer o bind do this em seu escopo.

ver post

Métodos do Array: map - Javascript

O método map em Javascript recebe uma função de callback e invoca ela para cada elemento do array, retornando um novo array.

ver post