Prueba de componentes de la interfaz de usuario de React

Que estoy haciendo ahora


Actualmente estoy probando una implementación de código. Dichas pruebas se rompen cada vez que se refactoriza, especialmente en los componentes de la interfaz de usuario. Como resultado, paso mucho tiempo cavando en los archivos .test.js , siguiendo simultáneamente la cifra mágica del 80% para la cobertura de prueba .


Que debo hacer


Al escribir cualquier tipo de prueba, incluidas las pruebas unitarias, debería pensar menos en el código que estoy probando y más en lo que hace este código. Esto significa escribir pruebas que imiten el comportamiento del usuario. Incluso en el nivel más bajo.


Ejemplo


Imagine un componente estándar de acordeón yuy. Cuando se presiona, se abre o se cierra. El contenido se pasa al componente como elementos children .


imagen


La funcionalidad de nuestro componente de prueba es la siguiente:


  • Se despliegan los primeros 3 acordeones, todos los restantes están cerrados.
  • Cuando hace clic en el acordeón, se publishAccordionAnalytics módulo publishAccordionAnalytics , que rastrea los análisis. Importamos este módulo desde el paquete @myProject/analyticsHelpers
  • Si el usuario hace clic en el acordeón oculto en la parte inferior de la aplicación, y después de abrir el contenido del acordeón no está en el campo de visión del usuario, se activa la animación y el contenido del componente se mueve a la parte visible de la pantalla.

 ... 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> ); } } 

Cómo escribiré una prueba ahora:


 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(); }) }); 

Pruebo la estructura del componente utilizando instantáneas, así como llamadas a funciones al hacer clic.


Este acordeón cumple totalmente con la funcionalidad esperada y las pruebas lo confirman. Pero ahora haré la refactorización, reemplazando React.Component con el functional component , y scrollToRef componente scrollToRef en una función 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> ); } 

Mis pruebas cayeron ... la prueba ' scrollToRef ' falla porque La función scrollToRef ya no es un método privado componente. Lo mismo sucedería con la prueba ' ' , pero es una importación del módulo, por lo que ahora es aprobada.


Para escribir una buena prueba, necesito entender cómo el usuario usa mi componente. Usuario:


  1. Encontró un acordeón que contiene la información que necesita
  2. Haga clic en el nombre del acordeón para abrirlo.
  3. Leí ifna
  4. Acordeon cerrado


Me di cuenta de que a él no le importa cómo se llama mi componente, en qué hace clic, etc. Siguiendo este principio, debería escribir algo como esto:


 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(); }); }); 

Ahora he reproducido el comportamiento del usuario. Encontré el décimo acordeón, hice clic en él, leí el contenido, lo cerré, ¡eso es!


Esta prueba es visualmente mucho más limpia, puede soportar la refactorización y simula la interacción del usuario. Y fue mucho más fácil para mí escribirlos.


Usando este patrón, podemos evitar el uso de la instance() Enzyme nativa instance() , state() , find('ComponentName') y otras funciones que prueban la implementación del código.


Para la nueva prueba, usé


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


All Articles