Testando componentes da interface do usuário do React

O que estou fazendo agora


Atualmente, estou testando uma implementação de código. Esses testes são interrompidos todas as vezes após a refatoração, especialmente nos componentes da interface do usuário. Como resultado, passo muito tempo pesquisando nos arquivos .test.js , seguindo simultaneamente a figura mágica de 80% para a Cobertura de teste .


O que devo fazer


Ao escrever qualquer tipo de teste, incluindo teste de unidade, devo pensar menos no código que estou testando e mais no que esse código faz. Isso significa escrever testes que imitam o comportamento do usuário. Mesmo no nível mais baixo.


Exemplo


Imagine um componente padrão de acordeão yuy. Quando pressionado, ele abre ou fecha. O conteúdo é passado para o componente como children .


imagem


A funcionalidade do nosso componente de teste é a seguinte:


  • Os três primeiros acordeões são implantados, todos os demais estão fechados.
  • Quando você clica no acordeão, o módulo publishAccordionAnalytics é publishAccordionAnalytics , que rastreia a análise. Importamos este módulo do pacote @myProject/analyticsHelpers
  • Se o usuário clicar no acordeão oculto na parte inferior do aplicativo e depois de abrir o conteúdo do acordeão não estiver no campo de visão do usuário, a animação será acionada e o conteúdo do componente será movido para a parte visível da tela.

 ... import { publishAccordeonAnalytics } from '@myProject/analyticsHelpers'; class Accordion extends Component { positionReference: RefObjectType; constructor(props) { super(props); this.positionReference = React.createRef(); } scrollToRef = (ref) => { const wrapper = document.querySelector('.app'); return isInViewPort(ref, wrapper) ? null : setTimeout(() => { return ref.current && wrapper && wrapper.scrollTo(wrapper.scrollTop, wrapper.scrollTop + ref.current.offsetTop) }, 300); }; render() { const { headerName, children, index } = this.props; const { scrollToRef, positionReference } = this; const defaultOpenAccordions = index >= 3; return ( <Accordion defaultOpen={!defaultOpenAccordions} headerName={headerName} onChange={(open) => { publishAccordionAnalytics(open, headerName); if (defaultOpenAccordions) { scrollToRef(positionReference); } }} id={headerName} > {children} {defaultOpenAccordions && <div data-testId="referenced-div" ref={positionReference} />} </Accordion> ); } } 

Como vou escrever um teste agora:


 jest.mock('@myProject/analyticsHelpers'); describe('', () => { test(' ', () => { const jsx = ( <Accordion headerName=" "> <div>Test</div> </Accordion> ); const tree = renderer .create(jsx) .toJSON(); expect(tree).toMatchSnapshot(); }); test(' ', () => { const wrapper = mount( <Accordion headerName=" " index={1}> <div>Test</div> </Accordion> ); wrapper.find('Header').simulate('click'); expect(publishAccordeonAnalytics).toHaveBeenCalledTimes(1); }); test(' scrollToRef ', () => { const wrapper = shallow( <Accordion headerName=" " index={7}> <div>Test</div> </Accordion>); const component = wrapper.instance(); component.scrollToRef = jest.fn(); wrapper.find('Header').simulate('click'); expect(publishAccordeonAnalytics).toHaveBeenCalledTimes(1); expect(component.scrollToRef).toHaveBeenCalled(); }) }); 

Testo a estrutura do componente usando instantâneos, bem como chamadas de função ao clicar.


Este acordeão atende totalmente à funcionalidade esperada e os testes confirmam isso. Mas agora vou fazer a refatoração, substituindo React.Component pelo functional component , e scrollToRef componente scrollToRef em uma função separada.


 function Accordion ({ marketName, children, index }) { const positionReference = React.createRef(); const defaultOpenAccordions = index >= config.defaultOpenAccordions; return ( <Accordion defaultOpen={!defaultOpenAccordions} headerName={headerName} onChange={(open) => { publishAccordeonAnalytics(open, marketName); if (defaultOpenAccordions) { scrollToRef(positionReference); } }} id={marketName} > {children} {defaultOpenAccordions && <div data-testId="referenced-div" ref={positionReference} />} </Accordion> ); } 

Meus testes caíram ... o teste ' scrollToRef ' falha porque A função scrollToRef não scrollToRef mais um método particular de componente. O mesmo aconteceria com o teste ' ' , mas é uma importação do módulo, então agora é aprovado.


Para escrever um bom teste, preciso entender como o usuário usa meu componente. Usuário:


  1. Encontrou um acordeão que contém as informações de que ele precisa
  2. Clicou no nome do acordeão para abri-lo
  3. Eu li ifna
  4. Acordeão fechado


Percebi que ele não se importa com o que meu componente é chamado, com o que ele clica e assim por diante. Seguindo esse princípio, eu deveria escrever algo assim:


 import '@testing-library/jest-dom/extend-expect'; import { render, fireEvent, screen } from '@testing-library/react'; jest.mock('@myProject/analyticsHelpers'); describe('', () => { test('  ', () => { const child = <div></div>; //  № 10   const { getByText } = render( <Accordion headerName="  10" index={9}> {child} </Accordion> ); //   fireEvent.click(getByText(/  10/i)); expect(publishAccordeonAnalytics).toHaveBeenCalled(); expect(screen.queryByText('')).toBeInTheDocument(); expect(screen.getByTestId('referenced-div')).toBeInTheDocument(); //   fireEvent.click(getByText(/  10/i)); expect(publishAccordeonAnalytics).toHaveBeenCalled(); }); }); 

Agora reproduzi o comportamento do usuário. Encontrei o 10º acordeão, cliquei nele, li o conteúdo, fechei - é isso!


Este teste é visualmente muito mais limpo, pode suportar refatoração e simula a interação do usuário. E foi muito mais fácil para mim escrevê-las.


Usando esse padrão, podemos evitar o uso da instance() Enzyme nativa instance() , state() , find('ComponentName') e outras funções que testam a implementação do código.


Para o novo teste, eu usei


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


All Articles