Test des composants React UI

Qu'est-ce que je fais maintenant


Je teste actuellement une implémentation de code. Ces tests se cassent à chaque fois après refactoring, en particulier dans les composants de l'interface utilisateur. En conséquence, je passe beaucoup de temps à creuser dans les fichiers .test.js , en suivant simultanément le chiffre magique de 80% pour la couverture de test .


Que dois-je faire


Lors de l'écriture de tout type de test, y compris les tests unitaires, je devrais penser moins au code que je teste, et plus à ce que fait ce code. Cela signifie écrire des tests qui imitent le comportement des utilisateurs. Même au niveau le plus bas.


Exemple


Imaginez un composant d'accordéon yuy standard. Lorsqu'il est pressé, il s'ouvre ou se ferme. Le contenu est transmis au composant en tant children .


image


La fonctionnalité de notre composant de test est la suivante:


  • Les 3 premiers accordéons sont déployés, tous les autres sont fermés.
  • Lorsque vous cliquez sur l'accordéon, le module publishAccordionAnalytics est publishAccordionAnalytics , qui suit les analyses. Nous importons ce module à partir du @myProject/analyticsHelpers
  • Si l'utilisateur clique sur l'accordéon caché au bas de l'application, et après avoir ouvert le contenu de l'accordéon n'est pas dans le champ de vision de l'utilisateur, l'animation est déclenchée et le contenu du composant se déplace vers la partie visible de l'écran.

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

Comment j'écrirai un test maintenant:


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

Je teste la structure du composant en utilisant des instantanés, ainsi que des appels de fonction au clic.


Cet accordéon répond pleinement aux fonctionnalités attendues et les tests le confirment. Mais maintenant, je vais refactoriser, en remplaçant React.Component par le functional component , et scrollToRef composant scrollToRef dans une fonction distincte.


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

Mes tests ont échoué ... le test ' scrollToRef ' échoue car La fonction scrollToRef n'est plus une méthode privée de composant. La même chose se produirait avec le test ' ' , mais c'est une importation à partir du module, alors maintenant c'est réussi.


Pour écrire un bon test, j'ai besoin de comprendre comment l'utilisateur utilise mon composant. Utilisateur:


  1. Trouvé un accordéon contenant les informations dont il a besoin
  2. Cliquer sur le nom de l'accordéon pour l'ouvrir
  3. Je lis ifna
  4. Accordéon fermé


J'ai réalisé qu'il ne se soucie pas de la façon dont mon composant est appelé, de ce sur quoi il clique, etc. En suivant ce principe, je devrais écrire quelque chose comme ceci:


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

Maintenant, j'ai reproduit le comportement de l'utilisateur. J'ai trouvé le 10e accordéon, cliqué dessus, lu le contenu, fermé - c'est tout!


Ce test est visuellement beaucoup plus propre, peut résister au refactoring et simule l'interaction de l'utilisateur. Et c'était beaucoup plus facile pour moi de les écrire.


En utilisant ce modèle, nous pouvons éviter l'utilisation de l' instance() Enzyme native instance() , state() , find('ComponentName') et d'autres fonctions qui testent l'implémentation du code.


Pour le nouveau test, j'ai utilisé


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


All Articles