Guia definitivo para testes de unidade em aplicativos React com Jest e React-Testing

Introdução

Como desenvolvedor, uma das coisas no topo da sua lista deve ser o envio de código livre de bugs. Nada poderia ser pior do que descobrir na noite de quinta-feira que as alterações feitas na segunda-feira interromperam o aplicativo ativo. A única maneira de garantir que seu aplicativo funcione de acordo com os requisitos do sistema e do usuário é teste-o!

O teste é um componente crucial de qualquer ciclo de vida de desenvolvimento de software e garante que um software opere adequadamente e de acordo com o planejado. O desenvolvimento da Web, o desenvolvimento de aplicativos móveis e, mais significativamente em nosso contexto, os aplicativos React seguem os mesmos princípios.

Os componentes do React podem ser testados de algumas maneiras diferentes, amplamente divididos em dois grupos:

  • Renderizando árvores de componentes em um ambiente de teste simples e fazendo afirmações sobre seu desempenho
  • Corrida “testes de ponta a ponta”, que envolve testar um aplicativo inteiro em um ambiente de navegador realista

Embora o teste de aplicativos React possa ser feito de várias maneiras, neste guia, criaremos um aplicativo React e cobriremos um guia completo sobre como podemos realizar testes de unidade no aplicativo React usando Brincadeira e Biblioteca de testes de reação para que você possa aprimorar suas habilidades de teste e aprender a criar um aplicativo React manso.

Observação: Você pode obter acesso ao repositório deste guia e brincar com tudo o que está nele, usando este link no GitHub.

O que é Testar?

Em primeiro lugar, vamos colocar as coisas em uma perspectiva. ensaio é um termo muito amplo e pode se referir a testes manuais, testes de unidade, testes de regressão, testes de integração, testes de carga, etc.

No contexto teste de unidade no qual vamos nos concentrar hoje - testamos o função de unidades distintas, normalmente em um nível de método. Isso pode testar os valores numéricos das saídas, o comprimento dos valores de saída, suas formas, como o método reage a entradas inválidas, etc.

Como a maioria das boas práticas de software defende métodos/funções curtos e acionáveis ​​que são independentes com um propósito claro, muitos métodos chamarão Outros métodos. Normalmente, você desejará testar os métodos internos e externos, para garantir que quaisquer alterações feitas durante a refatoração, correção de bugs ou melhoria de um recurso não interrompam nenhuma outra funcionalidade.

In Desenvolvimento Orientado a Testes (TDD), você é encorajado a escrever um teste e um valor esperado antes de escrever a lógica para um método. Naturalmente, ele falhará no início. Depois disso, basta fazê-lo funcionar e, quando passar no teste, você começa a refatorá-lo para torná-lo mais curto, mais limpo, mais rápido etc. Contanto que a saída permaneça a mesma, você sabe que não quebrou nada durante a refatoração!

Escrever seus próprios testes de unidade coloca você na mentalidade de alguém utilização seus métodos, em vez de alguém escrita esses métodos, que geralmente ajudam a dar uma nova olhada em um recurso, incorporam verificações e validações adicionais e caçam bugs. Às vezes, isso leva a alterações de design para tornar o código mais testável, como desacoplar a funcionalidade para permitir testes numéricos para cada componente individual.

Depois que uma linha de base é estabelecida e seu código passa nos testes, você pode fazer alterações e validar se as unidades individuais (normalmente métodos) funcionam individualmente. O teste é especialmente útil quando há atualizações em uma base de código.

Abordagens para Teste

O teste pode ser feito de duas maneiras diferentes: manualmente e automaticamente. Ao interagir diretamente com um aplicativo, o teste manual verifica se ele funciona corretamente. O teste automatizado é a prática de escrever programas para realizar as verificações para você.

Teste Manual

A maioria dos desenvolvedores revisa manualmente seu código, pois essa é a maneira mais rápida, natural e simples de testar rapidamente uma funcionalidade.

O teste manual é o próximo passo lógico que segue depois de escrever a funcionalidade, assim como provar um prato depois de temperá-lo (adicionar um recurso) para verificar se funcionou conforme o esperado.

Suponha que, como desenvolvedor empregado, você esteja criando um formulário de inscrição. Você não simplesmente fecha seu editor de texto e informa seu chefe que o formulário está completo após a codificação. Você abrirá o navegador, passará pelo processo do formulário de inscrição e garantirá que tudo corra conforme o planejado. Em outras palavras, você testará manualmente o código.

O teste manual é ideal para pequenos projetos e você não precisa de testes automatizados se tiver um aplicativo de lista de tarefas que pode ser verificado manualmente a cada dois minutos. No entanto, dependendo o teste manual se torna difícil à medida que seu aplicativo cresce – pode ser muito fácil perder a concentração e esquecer de verificar alguma coisa, talvez. Com uma lista crescente de componentes de interação, o teste manual torna-se ainda mais difícil, especialmente se você testou algo e progrediu para um novo item e quebrou o último recurso, para não testá-lo novamente por um tempo sem saber que agora está quebrado.

Simplificando, o teste manual é bom para começar – mas não escala bem e não garante a qualidade do código para projetos maiores. A boa notícia é que os computadores são incríveis em tarefas como essas, temos testes automatizados para agradecer!

Testes automatizados

No teste automatizado, você escreve código adicional para testar o código do aplicativo. Depois de escrever o código de teste, você pode teste seu aplicativo quantas vezes quiser com o mínimo de esforço.

Existem inúmeras técnicas para escrever testes automatizados:

  • Escrever programas para automatizar um navegador,
  • Chamando funções diretamente do seu código-fonte,
  • Comparando capturas de tela do seu aplicativo renderizado…

Cada técnica tem seu próprio conjunto de vantagens, mas todas têm uma coisa em comum – eles economizam seu tempo e garantem maior qualidade de código em relação ao teste manual!

Os testes automatizados são excelentes para garantir que seu aplicativo esteja funcionando conforme o planejado. Eles também facilitam a revisão de alterações de código em um aplicativo.

Tipos de teste

Até agora, examinamos testes de alto nível. É hora de discutir os vários tipos de testes que podem ser escritos.

Existem três tipos de testes de aplicativos front-end:

  • Testes unitários: Nos testes de unidade, unidades individuais ou componentes do software são testados. Uma unidade individual é uma única função, método, procedimento, módulo, componente ou objeto. Um teste de unidade isola e verifica uma seção de código para validar se cada unidade do código do software funciona conforme o esperado.
    Módulos ou funções individuais são testados em testes de unidade para garantir que estejam funcionando corretamente como deveriam, e todos os componentes também são testados individualmente. O teste de unidade incluiria, por exemplo, determinar se uma função, uma instrução ou um loop em um programa está funcionando corretamente.

  • Testes instantâneos: esse tipo de teste garante que a interface do usuário (IU) de um aplicativo da Web não seja alterada inesperadamente. Ele captura o código de um componente em um ponto específico no tempo, permitindo comparar o componente em um estado com qualquer outro estado possível que ele possa assumir.
    Um cenário típico de teste de instantâneo envolve renderizar um componente de interface do usuário, tirar um instantâneo e comparar o instantâneo com um arquivo de instantâneo de referência mantido com o teste. Se os dois instantâneos forem diferentes, o teste falhará porque a alteração foi inesperada ou o instantâneo de referência precisou ser atualizado para refletir o novo componente de interface do usuário.

  • Testes ponta a ponta: os testes de ponta a ponta são o tipo de teste mais fácil de entender. Testes de ponta a ponta em aplicativos front-end automatizam um navegador para garantir que um aplicativo funcione corretamente da perspectiva do usuário.
    Testes de ponta a ponta economizam muito tempo. Você pode executar um teste de ponta a ponta quantas vezes quiser depois de escrevê-lo. Considere quanto tempo um conjunto de centenas desses testes poderia economizar em comparação com a criação de testes para cada unidade individual.
    Com todos os benefícios que eles trazem, os testes de ponta a ponta têm alguns problemas. Para começar, os testes de ponta a ponta são demorados. Outro problema com os testes de ponta a ponta é que eles podem ser difíceis de depurar.

Observação: Para evitar problemas de reprodutibilidade, os testes de ponta a ponta podem ser executados em um ambiente reproduzível, como um Container Docker. Os contêineres do Docker e os testes de ponta a ponta estão além do escopo deste guia, mas você deve examiná-los se quiser executar testes de ponta a ponta para evitar o problema de falhas em máquinas diferentes.

Se você gostaria de compreender os fundamentos dos testes de ponta a ponta com o Cypress - leia nosso “Teste de ponta a ponta em JavaScript com Cypress”!

Vantagens e desvantagens do teste

Embora o teste seja importante e deva ser feito, como sempre, ele tem vantagens e desvantagens.

Vantagens

  • Protege contra regressão inesperada
  • Testar adequadamente aumenta significativamente a qualidade do código
  • Ele permite que o desenvolvedor se concentre na tarefa atual e não no passado
  • Ele permite a construção modular de aplicativos difíceis de construir
  • Elimina a necessidade de verificação manual

Desvantagens

  • Você precisa escrever mais código além de depurar e mantê-lo, e muitos acham que é uma sobrecarga desnecessária em projetos menores, independentemente dos benefícios
  • Falhas de teste não críticas/benignas podem resultar na rejeição do aplicativo durante a integração contínua

Visão geral do teste de unidade

Até agora, examinamos os testes em geral. Agora é a hora de mergulhar em tudo o que diz respeito aos testes de unidade e como escrever testes de unidade em aplicativos React!

Antes de definir o teste de unidade, é necessário que saibamos que uma boa abordagem de teste visa acelerar o tempo de desenvolvimento, reduzir bugs em um aplicativo e melhorar a qualidade do código, enquanto uma abordagem de teste ruim prejudicaria um aplicativo. Como resultado, como desenvolvedores de software, devemos aprender abordagens eficazes de teste de unidade, e uma delas é o teste de unidade.

Uma definição simples de teste é que é o processo de verificar se um aplicativo se comporta corretamente. O teste de unidade é o processo de execução de testes nos componentes ou funções de um aplicativo. Testes de unidade são funções que chamam versões isoladas das funções em seu código-fonte para verificar se elas se comportam como deveriam, de forma determinística.

Prós de testes de unidade

Os testes de unidade são rápidos e podem ser executados em alguns segundos (individualmente para um novo recurso ou executando globalmente todos os testes), dando aos desenvolvedores feedback imediato sobre se um recurso está quebrado ou não. Eles também ajudam a fornecer documentação, porque se um novo desenvolvedor ingressar em um projeto, ele precisará saber como várias unidades da base de código se comportam; isso pode ser conhecido observando os resultados dos testes de unidade.

Contras dos testes de unidade

Embora os testes de unidade tenham seus lados bons, eles também têm seus próprios problemas. Um problema é que a refatoração do código quando se trata de alterações de design, pois eles tendem a ser mais difíceis com testes de unidade. Digamos, por exemplo, que você tenha uma função complicada com seus testes de unidade e queira dividir essa função em várias funções modulares. Um teste de unidade provavelmente falhará para essa função e você precisará descontinuar e escrever dois testes de unidade para as funções de divisão. É por isso que o teste de unidade implicitamente incentiva a divisão inicial e o teste individual, levando a componentes de código modulares mais testáveis. No entanto, em alguns casos, você não pode prever as possíveis alterações no futuro, e a quantidade de tempo que levaria para atualizar os testes de unidade torna os processos de refatoração sérios menos atraentes.

Outro problema com o teste de unidade é que ele verifica apenas partes individuais de um aplicativo, mesmo que essa parte seja uma combinação lógica de várias partes menores – não há teste de unidade para toda a aplicação. Partes individuais de um aplicativo podem funcionar corretamente, mas se não for testado como eles se comportam quando combinados, os testes podem se tornar inúteis. É por isso que os testes de unidade devem ser complementados com testes de ponta a ponta ou testes de integração ou, idealmente, ambos.

Unidade de teste de um aplicativo React - Projeto de demonstração

Vamos dar uma olhada em um exemplo do mundo real de teste de unidade em um aplicativo React!

Nesta demonstração, testaremos um aplicativo Counter com várias partes diferentes. Apesar de parecer um aplicativo bastante simples, serviria como um bom exemplo para aprender como funcionam os testes de unidade. A essência de testar este aplicativo é que existem diferentes aspectos do componente que dependem de como o usuário está interagindo com ele.

Configuração do Projeto

A create-react-app O comando, desenvolvido pela equipe do React, é a melhor maneira de começar a criar um aplicativo React real e de grande escala porque está pronto para uso e funciona facilmente com o Biblioteca de testes Jest. Se você abrir o package.json arquivo, você descobrirá que temos suporte padrão para Brincadeira e os votos de Biblioteca de teste de reação no setupTests.js arquivo. Isso elimina a necessidade de instalar manualmente o Jest em nosso projeto, se necessário!

Se você ainda não o usou - execute-o com npx, que irá instalá-lo para uso posterior:

$ npx create-react-app react-unit-tests

Se você já tiver a ferramenta instalada, crie um aplicativo React e nomeie-o react-unit-tests:

$ create-react-app react-unit-tests

Observação: npx usa a versão mais recente do create-react-app, enquanto uma versão instalada globalmente pode não. Geralmente é aconselhável executar a ferramenta através npx para garantir as versões mais recentes, a menos que você queira usar outra versão propositalmente.

Em seguida, entramos no diretório do projeto e iniciamos o servidor de desenvolvimento:

$ cd react-unit-tests && npm start
// OR
$ cd react-unit-tests && yarn start

Isso produzirá nosso aplicativo recém-criado no navegador em localhost:3000.

Observação: Um recurso útil aqui é que o recarregamento a quente é suportado por padrão, então não há necessidade de ficar recarregando o navegador apenas para ver as novas alterações, ou instalar manualmente nodemon ou bibliotecas semelhantes.

Construindo o componente do contador

No src diretório do nosso projeto, crie um novo arquivo chamado Counter.js. em Counter.js, definiremos todas as partes do componente. Ele conterá várias funções do contador, incluindo increment(), decrement(), restart() e switchSign(), que inverte o valor de contagem de negativo para positivo quando clicado. Essas funções são criadas para manipular o valor de contagem inicial (que é passado como prop):


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount((prev) => prev + 1);
  };

  const decrement = () => {
    setCount((prev) => prev - 1);
  };

  const restart = () => {
    setCount(0);
  };

  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
        Count: <h3>{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

Em seguida, atualize App.js:


import "./App.css";
import Counter from "./Counter";

function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

export default App;

Agora, podemos visualizar o aplicativo contador no navegador:

Guia definitivo para testes unitários em aplicativos React com Jest e React-Testing PlatoBlockchain Data Intelligence. Pesquisa vertical. Ai.

Criando testes para componentes

Vamos criar um arquivo de teste chamado Counter.test.js para representar o teste para o componente Counter. Certifique-se de excluir também App.test.js para que não crie resultados indesejados enquanto executamos testes.

Observação: Uma prática comum é nomear seus arquivos de teste com um sufixo de .test.js, espelhando o nome do arquivo/componente que você está testando. Isso garante uma continuidade entre os arquivos de teste, as alterações são feitas apenas nos arquivos relevantes para o código que você está atualizando ao enviar alterações (menor número de conflitos de mesclagem) e é legível.

Além disso, os arquivos de teste geralmente estão localizados em um /test anuário paralelo para o diretório raiz do seu código-fonte, porém, isso também depende da equipe.

In Counter.test.js, primeiro importamos o Counter componente, em seguida, inicie o teste com o describe() função para descrever todas as diferentes funcionalidades que podem acontecer dentro do componente.

A describe() A função é usada para agrupar conjuntos específicos de testes que podem ocorrer em um componente usando vários it() e test() métodos. É uma espécie de wrapper lógico, no qual você, bem, descreve o que uma série de testes faz, com cada it() sendo um teste funcional para de uma unidade.

O teste de seus componentes React pode ser feito de maneira que possamos usar um renderizador de teste para criar rapidamente um valor serializável para sua árvore React, em vez de gerar a interface gráfica do usuário, o que envolveria a criação do aplicativo completo.

Testando o valor inicial do contador

Ao testar, ajuda a criar uma lista sistemática de recursos e aspectos de um determinado recurso – os estados em que os componentes podem estar, o que poderia afetá-los, etc.

A primeira coisa que vamos testar é o valor de contagem inicial e como o componente está lidando com o prop que o define. Com o it() método, verificamos se o aplicativo contador está realmente exibindo o valor de contagem inicial exato que foi passado como prop, que é 0 neste caso, e passe uma função callback que descreva todas as ações que ocorrerão dentro do teste:


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByTestId("count").textContent).toEqual(0);
  });
});

Aqui, usamos o screen instância da biblioteca React Testing para renderizar o componente para fins de teste. É útil renderizar uma versão simulada de um componente a ser testado. E desde o

elemento que contém o count valor é obrigado a mudar dinamicamente, usamos o screen.getByTestId() função para ouvi-lo e buscar seu valor com o textContent propriedade.

Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!

Observação: A screen A função retorna um nó DOM correspondente para qualquer consulta ou lança um erro se nenhum elemento for encontrado.

Então, no Counter.js componente, vamos ouvir o

elemento durante o teste, definindo um data-testid atributo ao elemento com um valor count:


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  const increment = () => {
    setCount((prev) => prev + 1);
  };
  const decrement = () => {
    setCount((prev) => prev - 1);
  };
  const restart = () => {
    setCount(0);
  };
  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
      	
        Count: <h3 data-testid="count">{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

Para testar se a inicial count valor é igual a 0, nós usamos o expect() método para descrever o que é esperado do teste que definimos! Em nosso caso, esperamos que o valor de contagem inicial seja 0 então usamos o toEqual() método, que é usado para determinar se os valores de dois objetos correspondem. Em vez de determinar a identidade do objeto, o toEqual() matcher verifica recursivamente todos os campos quanto à igualdade.

Observação: O teste é focado especificamente nas informações que você renderiza; em nosso exemplo, ou seja, o Counter componente que recebeu um initialCount suporte. Isso sugere que mesmo que outro arquivo - digamos, vamos App.js- tem adereços faltando no Counter componente, o teste ainda será aprovado porque é focado exclusivamente em Counter.js e não sabe usar o Counter componente. Além disso, como os testes são independentes um do outro, renderizar o mesmo componente com props diferentes em outros testes também não afetará.

Agora, podemos executar o teste de conjunto:

$ yarn test

O teste deve falhar:

FAIL  src/Counter.test.js
  Counter
    × counter displays correct initial count (75 ms)

  ● Counter › counter displays correct initial count

    expect(received).toEqual(expected) // deep equality

    Expected: 0
    Received: "0"

       5 |   it("counter displays correct initial count", () => {
       6 |     render();
    >  7 |     expect(screen.getByTestId("count").textContent).toEqual(0);
         |                                                     ^
       8 |   });
       9 | });
      10 |

      at Object. (src/Counter.test.js:7:53)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.929 s, estimated 2 s
Ran all test suites related to changed files.

Este teste falhou porque testamos um número em uma string, o que resultou em um erro de igualdade profunda. Para corrigir isso, casto que o textContent, ou seja, o valor inicial, em nossa função callback como um número:


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
     
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });
});

Agora, nosso código passará no primeiro teste:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.271 s
Ran all test suites related to changed files.

Este é um exemplo simples de como testar enquanto a lógica de escrita ajuda a evitar problemas no futuro, antes que a dívida tecnológica se acumule ainda mais. Testar prematuramente também pode prendê-lo, já que refatorar e alterar a lógica é mais caro em termos de tempo se você também tiver que reescrever os testes.

Encontrar um bom equilíbrio pode ajudar a melhorar a qualidade do seu software, com um efeito negativo mínimo na sua produtividade e velocidade.

Testando o botão de incremento

Para testar que o increment botão funciona como deveria, ou seja, para incrementar o count valor por um cada vez que é clicado, precisamos primeiro acessar o increment botão, então definimos um novo it() método para o mesmo.

Como o valor do botão não é dinâmico, ou seja, sempre terá o valor Increment dentro dele, usamos o getByRole() método em vez do getByTestId() para consultar o DOM.

Quando se utiliza o getByRole() método, uma função descreve um elemento HTML.

Também devemos passar um objeto para definir qual botão, em particular, desejamos testar, pois pode haver muitos botões quando o DOM é renderizado. No objeto, definimos um name com um valor que deve ser igual ao texto no botão de incremento.

A próxima coisa a fazer é simular um evento de clique usando o fireEvent() método, que permite disparar eventos que simulam ações do usuário durante o teste.

Primeiro, escrevemos um teste para ver se o valor de contagem aumenta em 1 de seu valor inicial de 0:


import { fireEvent, render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });

  it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue = Number(screen.getByTestId("count").textContent);
    expect(countValue).toEqual(1);
  });
});

Isto resulta em:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (66 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.405 s
Ran all test suites related to changed files.

Então, também podemos escrever um teste para verificar se o count valor era 0 antes do botão ser clicado, definindo dois expect() métodos – um antes do evento click ser acionado e outro após o evento click ser acionado:


it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    let countValue1 = Number(screen.getByTestId("count").textContent);
    expect(countValue1).toEqual(0);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue2 = Number(screen.getByTestId("count").textContent);
    expect(countValue2).toEqual(1);
});

Os testes ainda passaram:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (82 ms)
    √ count should increment by 1 if increment button is clicked (60 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.388 s
Ran all test suites related to changed files.

Testando o botão de decremento

Da mesma forma, escrevemos o teste para o Increment botão, definimos o teste para o Decrement botão assim:


it("count should decrement by 1 if decrement button is clicked", () => {
  render(<Counter initialCount={0} />);
  fireEvent.click(screen.getByRole("button", { name: "Decrement" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(-1);
});

Isto resulta em:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (73 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.346 s
Ran all test suites related to changed files.

Testando o botão Reiniciar

Semelhante ao Increment e Decrement botões, definimos o teste para o Restart botão assim:


it("count should reset to 0 if restart button is clicked", () => {
  render(<Counter initialCount={50} />);
  fireEvent.click(screen.getByRole("button", { name: "Restart" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(0);
});

Para fins de teste, o valor inicial foi definido como 50 (valor arbitrário) e, quando o teste é executado, todos os quatro testes passam com sucesso:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)
    √ count should increment by 1 if increment button is clicked (57 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (16 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.583 s
Ran all test suites related to changed files.

Testando o botão de sinal de troca

Também escrevemos o teste para inverter o sinal no count valor definindo o valor de count para 50 no arquivo de teste. Em seguida, observe qual sinal é renderizado antes e depois de um evento de clique ser disparado pelo botão:


it("count invert signs if switch signs button is clicked", () => {
  render(<Counter initialCount={50} />);
  let countValue1 = Number(screen.getByTestId("count").textContent);
  expect(countValue1).toEqual(50);
  fireEvent.click(screen.getByRole("button", { name: "Switch signs" }));
  let countValue2 = Number(screen.getByTestId("count").textContent);
  expect(countValue2).toEqual(-50);
});

Isto resulta em:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (91 ms)
    √ count should increment by 1 if increment button is clicked (72 ms)
    √ count should decrement by 1 if increment button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (19 ms)
    √ count invert signs if switch signs button is clicked (14 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        3.104 s
Ran all test suites related to changed files.

Wooshh! Todos os testes foram aprovados com sucesso para nosso aplicativo contador.

Escrever testes não é difícil - simulamos efetivamente os casos de uso de um recurso para garantir que ele não seja interrompido quando usado conforme pretendido e não intencional. Alguém forneceu um valor fora dos limites? Um formato errado? O aplicativo deve resolver o problema em vez de falhar.

Em geral, um bom ponto de partida para o teste é:

  • Teste o comportamento pretendido (quaisquer que sejam seus recursos)
  • Teste todas as facetas de comportamento não intencional (entradas incorretas, como formatos não suportados, limites, etc.)
  • Teste numericamente (se seu recurso produzir valores numéricos que possam ser verificados, calcule o resultado manualmente e verifique se ele retorna a saída correta)

Práticas recomendadas para testes de unidade

  • Os testes devem ser determinísticos: Executar os mesmos testes no mesmo componente várias vezes deve produzir os mesmos resultados todas as vezes. Você deve garantir que seus instantâneos gerados não contenham dados específicos da plataforma ou outros dados não determinísticos.

  • Evite exames desnecessários: Bons testes não vêm com expectativas ou casos de teste desnecessários.
    Podemos entender melhor observando os testes abaixo:

test('the success modal is visible', () => {});
test('the success modal has a success message', () => {});

Se soubermos que a mensagem de sucesso dentro do modal de sucesso está visível, isso significa que o próprio modal de sucesso também está visível. Portanto, neste caso, podemos remover com segurança o primeiro teste e executar apenas o segundo ou combiná-los. Ter muitos testes pode dar uma falsa sensação de segurança se forem supérfluos.

  • Evite expor a lógica interna: Se seu teste executa uma ação que seu usuário não executa (como testar um método interno que não é exposto ao usuário), provavelmente você está testando detalhes de implementação. Você pode acabar expondo uma função privada apenas para testar seu componente. Este é um cheiro de código que deve ser evitado. Em vez disso, reestruture sua base de código para que a função privada seja testável sem expô-la publicamente.

  • Evite testar detalhes de implementação: Se nosso aplicativo incrementar x e y - se x é incrementado primeiro ou provavelmente não tem significado, desde que o resultado seja o mesmo. Você deve sempre refatorar, alterar e atualizar os detalhes da implementação sem quebrar os testes, caso contrário, os testes catalisariam o acúmulo de dívidas de tecnologia, aumentando o custo de refatoração e otimização.

  • Coloque a lógica de negócios em funções puras em vez de componentes de interface do usuário.

Conclusão

Este guia é principalmente sobre testes de unidade. No entanto, era importante primeiro entendermos e apreciarmos tudo o que envolve o teste, incluindo o que significa, abordagens de teste, tipos de teste e suas vantagens e desvantagens.

É fundamental lembrar por que você está escrevendo testes enquanto os escreve. Normalmente, o objetivo de escrever testes é economizar tempo. Os testes pagam dividendos se o projeto em que você está trabalhando for estável e for desenvolvido por um longo tempo. Com isso, é seguro dizer que testar um aplicativo pode não valer a pena, se não economizar tempo de desenvolvimento. Acima de tudo, bons testes são simples de manter e fornecem confiança ao alterar seu código.

Também aprendemos como consultar o DOM enquanto testamos aplicativos React usando o getByTestId() método. Ele é útil para definir contêineres e consultar elementos com texto dinâmico, mas não deve ser sua consulta padrão. Em vez de usar o getByTestId() método imediatamente, tente um destes primeiro:

  • getByRole() – consulta um elemento ao mesmo tempo em que garante que ele seja acessível com a função e o texto corretos
  • getByLabelText() – é uma consulta excelente para interagir com os elementos do formulário, também verifica se nossos rótulos estão devidamente vinculados às nossas entradas por meio dos atributos for e id
  • getByText() – Quando nenhuma das duas consultas anteriores estiver disponível, o getByText() método será útil para acessar elementos com base no texto que é visível para o usuário
  • getByPlaceholderText(): esta consulta é muito preferível a um ID de teste quando tudo o que você precisa para consultar um elemento é um espaço reservado.

Esperamos que este guia seja útil para você! Você pode obter acesso ao repositório deste guia e brincar com tudo o que está nele, usando este link no GitHub.

Carimbo de hora:

Mais de Abuso de pilha