Uma maneira conveniente de testar os componentes do React

Escrevi um construtor de relatórios personalizado para o Jest e o publiquei no GitHub . Meu construtor é chamado Jest-snapshots-book, ele cria um livro HTML de snapshots dos componentes de um aplicativo React.



O artigo discutirá o que é Jest, teste de instantâneo, que geralmente requer um construtor de relatórios adicional e como escrevê-los. Basicamente, tudo isso se aplica ao teste de componentes React, mas teoricamente pode ser aplicado ao trabalhar com qualquer dado serializável.

Componente Reagir Paginator


Por exemplo, no artigo, testaremos o componente paginador ( Paginator ). Faz parte do nosso projeto para criar aplicativos sem servidor na AWS ( GitHub ). A tarefa desse componente é exibir botões para navegar pelas páginas de uma tabela ou outra coisa.

Este é um componente funcional simples sem componente sem estado (componente sem estado). Como entrada, ele recebe dos objetos propostos o número total de páginas, a página atual e a função de manipulador de clicar na página. Na saída, o componente produz um paginador formado. Para exibir os botões, outro componente filho do botão é usado. Se houver muitas páginas, o paginador não exibirá todas elas, combinando-as e exibindo-as na forma de reticências.



Código do componente do paginador
import React from 'react'; import classes from './Paginator.css'; import Button from '../../UI/Button/Button'; const Paginator = (props) => { const { tp, cp, pageClickHandler } = props; let paginator = null; if (tp !== undefined && tp > 0) { let buttons = []; buttons.push( <Button key={`pback`} disabled={cp === 1} clicked={(cp === 1 ? null : event => pageClickHandler(event, 'back'))}> ← </Button> ); const isDots = (i, tp, cp) => i > 1 && i < tp && (i > cp + 1 || i < cp - 1) && (cp > 4 || i > 5) && (cp < tp - 3 || i < tp - 4); let flag; for (let i = 1; i <= tp; i++) { const dots = isDots(i, tp, cp) && (isDots(i - 1, tp, cp) || isDots(i + 1, tp, cp)); if (flag && dots) { flag = false; buttons.push( <Button key={`p${i}`} className={classes.Dots} disabled={true}> ... </Button> ); } else if (!dots) { flag = true; buttons.push( <Button key={`p${i}`} disabled={i === cp} clicked={(i === cp ? null : event => pageClickHandler(event, i))}> {i} </Button> ); } } buttons.push( <Button key={`pforward`} disabled={cp === tp} clicked={(cp === tp ? null : event => pageClickHandler(event, 'forward'))}> → </Button> ); paginator = <div className={classes.Paginator}> {buttons} </div> } return paginator; } export default Paginator; 
Código do componente do botão
 import React from 'react'; import classes from './Button.css'; const button = (props) => ( <button disabled={props.disabled} className={classes.Button + (props.className ? ' ' + props.className : '')} onClick={props.clicked}> {props.children} </button> ); export default button; 

Jest


Jest é uma conhecida biblioteca de código-fonte aberto para teste de código JavaScript. Foi criado e desenvolvido graças ao Facebook. Escrito em Node.js.

Em termos gerais, o significado de teste se resume ao fato de que você precisa criar parâmetros de entrada para o seu código e descrever imediatamente a saída que seu código deve produzir. Ao executar testes, o Jest executa seu código com parâmetros de entrada e verifica o resultado com o esperado. Se corresponder, o teste será aprovado e, caso contrário, não será aprovado.

Um pequeno exemplo de jestjs.io .

Suponha que tenhamos um módulo Node.js., que é uma função que adiciona dois números (arquivo sum.js. ):

 function sum(a, b) { return a + b; } module.exports = sum; 

Se nosso módulo é salvo em um arquivo, para testá-lo, precisamos criar o arquivo sum.test.js no qual escrever esse código para teste:

 const sum = require('./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); 

Neste exemplo, usando a função de teste , criamos um teste chamado 'adiciona 1 + 2 para igual a 3' . O segundo parâmetro para a função de teste , passamos por uma função que realmente executa o teste.

O teste consiste no fato de que executamos nossa função de soma com os parâmetros de entrada 1 e 2 , passamos o resultado para a função Jest expect () . Então, usando a função Jest toBe () , o resultado transmitido é comparado com o esperado ( 3 ). A função toBe () pertence à categoria de funções de teste Jest (matchers).

Para testar, basta ir para a pasta do projeto e chamar gracejo na linha de comando. O Jest encontrará o arquivo com a extensão .test.js e executará o teste. Aqui está o resultado que ele produzirá:

 PASS ./sum.test.js ✓ adds 1 + 2 to equal 3 (5ms) 

Teste de enzima e instantâneo de componentes


O teste de instantâneo é um recurso relativamente novo no Jest. O ponto é que, com a ajuda de uma função de teste especial, solicitamos ao Jest que salve um instantâneo de nossa estrutura de dados em disco e, durante as execuções subsequentes, compare novos instantâneos com os salvos anteriormente.

O instantâneo, neste caso, nada mais é do que uma representação textual dos dados. Por exemplo, uma captura instantânea de algum objeto terá a seguinte aparência (a chave da matriz aqui é o nome do teste):

 exports[`some test name`] = ` Object { "Hello": "world" } `; 

É assim que a função de teste Jest se parece, que executa a comparação de imagens (parâmetros opcionais):

 expect(value).toMatchSnapshot(propertyMatchers, snapshotName) 

O valor pode ser qualquer estrutura de dados serializável. Pela primeira vez, a função toMatchSnapshot () simplesmente grava o instantâneo no disco; em momentos subsequentes, ele já executará a comparação.

Na maioria das vezes, essa tecnologia de teste é usada especificamente para testar componentes do React e, com mais precisão, para testar a renderização correta dos componentes do React. Para fazer isso, você precisa passar o componente como valor após a renderização.

Enzyme é uma biblioteca que simplifica bastante o teste de aplicativos React, fornecendo funções convenientes de renderização de componentes. A enzima é desenvolvida no Airbnb.

A enzima permite renderizar componentes no código. Existem várias funções convenientes para isso, que executam diferentes opções de renderização:

  • renderização completa (como no navegador, renderização completa do DOM);
  • renderização simplificada (renderização superficial);
  • renderização estática

Não nos aprofundaremos nas opções de renderização; para testes de captura instantânea, a renderização estática é suficiente, o que permite obter o código HTML estático do componente e de seus componentes filhos:

 const wrapper = render(<Foo title="unique" />); 

Portanto, renderizamos nosso componente e passamos o resultado para expect () e, em seguida, chamamos a função .toMatchSnapshot () . A função it é apenas uma abreviação para a função de teste .

 ... const wrapper = render(<Paginator tp={tp} cp={cp} />); it(`Total = ${tp}, Current = ${cp}`, () => { expect(wrapper).toMatchSnapshot(); }); ... 

Cada vez que o teste é executado, toMatchSnapshot () compara dois instantâneos: esperado (que foi gravado anteriormente no disco) e atual (que foi obtido durante o teste atual).

Se as imagens forem idênticas, o teste é considerado aprovado. Se houver uma diferença nas imagens, o teste será considerado não aprovado e o usuário verá a diferença entre as duas imagens na forma de diff (como nos sistemas de controle de versão).

Aqui está um exemplo de saída Jest quando o teste falha. Aqui vemos que temos um botão adicional na imagem atual.



Nessa situação, o usuário deve decidir o que fazer. Se as alterações no instantâneo forem planejadas devido a alterações no código do componente, ele deverá substituir o instantâneo antigo por um novo. E se as alterações forem inesperadas, será necessário procurar um problema no seu código.

Vou dar um exemplo completo para testar um paginador (arquivo Paginator.test.js ).

Para um teste mais conveniente do paginador, criei uma função de snapshoot (tp, cp) que terá dois parâmetros: o número total de páginas e a página atual. Esta função executará um teste com os parâmetros fornecidos. Tudo o que resta é chamar a função snapshoot () com vários parâmetros (mesmo em um loop) e testar, testar ...

 import React from 'react'; import { configure, render } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Paginator from './Paginator'; configure({ adapter: new Adapter() }); describe('Paginator', () => { const snapshoot = (tp, cp) => { const wrapper = render(<Paginator tp={tp} cp={cp} />); it(`Total = ${tp}, Current = ${cp}`, () => { expect(wrapper).toMatchSnapshot(); }); } snapshoot(0, 0); snapshoot(1, -1); snapshoot(1, 1); snapshoot(2, 2); snapshoot(3, 1); for (let cp = 1; cp <= 10; cp++) { snapshoot(10, cp); } }); 

Por que você precisou de um construtor de relatórios adicional


Quando comecei a trabalhar com essa tecnologia de teste, o sentimento de abordagem inicial inacabada não me deixou. Afinal, as imagens só podem ser vistas como texto.

Mas e se algum componente produzir muito código HTML ao renderizar? Aqui está um componente do paginador de 3 botões. Um instantâneo de um componente desse tipo será semelhante a este:

 exports[`Paginator Total = 1, Current = -1 1`] = ` <div class="Paginator" > <button class="Button" > ← </button> <button class="Button" > 1 </button> <button class="Button" > → </button> </div> `; 

Primeiro, você precisa garantir que a versão original do componente seja renderizada corretamente. Não é muito conveniente fazer isso simplesmente visualizando o código HTML em forma de texto. Mas estes são apenas três botões. E se você precisar testar, por exemplo, uma mesa ou algo ainda mais volumoso? Além disso, para testes completos, você precisa ver muitas fotos. Será bastante inconveniente e difícil.

Então, se o teste falhar, você precisa entender como a aparência dos componentes difere. Diferentemente do código HTML, é claro, permitirá que você entenda o que mudou, mas, novamente, a oportunidade de ver pessoalmente a diferença não será supérflua.

Em geral, pensei que seria necessário que as imagens pudessem ser visualizadas no navegador da mesma forma que aparentam no aplicativo. Incluindo os estilos aplicados a eles. Então, tive a ideia de melhorar o processo de teste de snapshot escrevendo um construtor de relatórios adicional para o Jest.

Olhando para o futuro, foi o que consegui. Cada vez que executo os testes, meu construtor atualiza o livro de instantâneos. Diretamente no navegador, é possível visualizar os componentes como eles aparecem no aplicativo, bem como ver imediatamente o código fonte das imagens e do diff (se o teste falhar).



Construtores de relatórios Jest


Os criadores do Jest deram a oportunidade de escrever criadores de relatórios adicionais. Isso é feito da seguinte maneira. Você precisa gravar um módulo no Node.JS que deve ter um ou mais destes métodos: onRunStart , onTestStart , onTestResult , onRunComplete , que correspondem a vários eventos de progresso do teste.

Então você precisa conectar seu módulo na configuração Jest. Existe uma diretiva de repórteres especial para isso. Se você deseja incluir seu construtor além disso, é necessário adicioná-lo ao final da matriz de repórteres .

Depois disso, o Jest chamará métodos do seu módulo em determinados estágios da execução do teste, passando os resultados atuais para os métodos. O código desses métodos, de fato, deve criar relatórios adicionais necessários. Portanto, em termos gerais, parece a criação de criadores de relatórios adicionais.

Como o Jest-snapshots-book funciona


Não insiro especificamente o código do módulo no artigo, pois o aprimorarei ainda mais. Pode ser encontrado no meu GitHub, este é o arquivo src / index.js na página do projeto.

Meu construtor de relatórios é chamado após a conclusão dos testes. Coloquei o código no método onRunComplete (contextos, resultados) . Funciona da seguinte maneira.

Na propriedade results.testResults , o Jest passa uma matriz de resultados de teste para essa função. Cada resultado do teste inclui um caminho para o arquivo de teste e uma matriz de mensagens com os resultados. Meu construtor de relatórios procura cada arquivo de teste com um arquivo de captura instantânea correspondente. Se um arquivo de captura instantânea for detectado, o construtor de relatórios criará uma página HTML no livro de capturas instantâneas e a gravará na pasta livro de capturas instantâneas na pasta raiz do projeto.

Para gerar uma página HTML, o construtor de relatórios, usando a função recursiva grabCSS (moduleName, css = [], level = 0), coleta todos os estilos, iniciando no componente em teste e descendo a árvore de todos os componentes importados. Portanto, a função coleta todos os estilos necessários para que o componente seja exibido corretamente. Os estilos coletados são adicionados à página HTML do livro de instantâneos.

Eu uso módulos CSS em meus projetos, portanto, não tenho certeza se isso funcionará se os módulos CSS não forem usados.

Se o teste for aprovado, o construtor inserirá um iFrame na página HTML com a imagem em duas opções de exibição: o código-fonte (a imagem como ela é) e o componente após a renderização. A opção de exibição no iFrame é alterada clicando no mouse.

Se o teste não foi aprovado, tudo ficará mais complicado. O Jest fornece, neste caso, apenas a mensagem exibida no console (veja a captura de tela acima).

Ele contém diferenças e informações adicionais sobre o teste que falhou. De fato, neste caso, estamos lidando essencialmente com duas imagens: a esperada e a real . Se tivermos o esperado - ele é armazenado no disco na pasta de instantâneos, o instantâneo Jest atual não fornece.

Portanto, tive que escrever um código que aplique a diferença Jest tirada da mensagem ao instantâneo esperado e crie um instantâneo real com base no esperado. Depois disso, o construtor exibe ao lado do iFrame o instantâneo esperado do iFrame do instantâneo atual, que pode alterar seu conteúdo entre três opções: o código-fonte, o componente após a renderização, diff.

É assim que a saída do construtor de relatórios se você definir a opção verbose = true para ela.



Links úteis



PS


O teste de captura instantânea não é suficiente para testar completamente um aplicativo React. Ele cobre apenas a renderização de seus componentes. Também é necessário testar seu funcionamento (reações às ações do usuário, por exemplo). No entanto, o teste de instantâneo é uma maneira muito conveniente de garantir que seus componentes sejam renderizados conforme o esperado. E o jest-snapshots-book facilita o processo.

Source: https://habr.com/ru/post/pt421647/


All Articles