Dezvoltare React.js bazată pe teste: testare unitară React.js cu Enzyme și Jest
Publicat: 2022-03-11Orice bucată de cod care nu are teste se spune că este cod moștenit, potrivit lui Michael Feathers. Prin urmare, una dintre cele mai bune modalități de a evita crearea de cod moștenit este utilizarea dezvoltării bazate pe teste (TDD).
Deși există multe instrumente disponibile pentru testarea unitară JavaScript și React.js, în această postare, vom folosi Jest și Enzyme pentru a crea o componentă React.js cu funcționalitate de bază folosind TDD.
De ce să folosiți TDD pentru a crea o componentă React.js?
TDD aduce multe beneficii codului dvs. — unul dintre avantajele acoperirii înalte a testelor este că permite refactorizarea ușoară a codului, menținând în același timp codul curat și funcțional.
Dacă ați creat o componentă React.js înainte, v-ați dat seama că codul poate crește foarte repede. Se umple cu o mulțime de condiții complexe cauzate de declarații legate de schimbările de stare și apelurile de service.
Fiecare componentă fără teste unitare are cod moștenit care devine dificil de întreținut. Am putea adăuga teste unitare după ce creăm codul de producție. Cu toate acestea, este posibil să riscăm să trecem cu vederea unele scenarii care ar fi trebuit testate. Prin crearea mai întâi de teste, avem o șansă mai mare de a acoperi fiecare scenariu logic din componenta noastră, ceea ce ar facilita refactorizarea și întreținerea.
Cum testăm unitatea o componentă React.js?
Există multe strategii pe care le putem folosi pentru a testa o componentă React.js:
- Putem verifica că o anumită funcție din elemente de
props
a fost apelată atunci când este trimis un anumit eveniment. - De asemenea, putem obține rezultatul funcției de
render
, având în vedere starea componentei curente și să-l potrivim cu un aspect predefinit. - Putem chiar verifica dacă numărul copiilor componentei se potrivește cu o cantitate așteptată.
Pentru a folosi aceste strategii, vom folosi două instrumente care ne sunt utile pentru a lucra cu teste în React.js: Jest și Enzyme.
Utilizarea Jest pentru a crea teste unitare
Jest este un cadru de testare open-source creat de Facebook, care are o integrare excelentă cu React.js. Include un instrument de linie de comandă pentru execuția testului similar cu ceea ce oferă Jasmine și Mocha. De asemenea, ne permite să creăm funcții simulate cu o configurație aproape zero și oferă un set foarte frumos de potriviri care face afirmațiile mai ușor de citit.
În plus, oferă o caracteristică foarte frumoasă numită „testare instantanee”, care ne ajută să verificăm și să verificăm rezultatul randării componentelor. Vom folosi testarea instantanee pentru a capta arborele unei componente și a-l salva într-un fișier pe care îl putem folosi pentru a o compara cu un arbore de randare (sau orice altceva transmitem funcției expect
ca prim argument.)
Utilizarea enzimei pentru a monta componentele React.js
Enzyme oferă un mecanism pentru a monta și traversa arborii componente React.js. Acest lucru ne va ajuta să obținem acces la propriile proprietăți și stat, precum și la recuzita copiilor, pentru a ne rula afirmațiile.
Enzyme oferă două funcții de bază pentru montarea componentelor: shallow
și mount
. Funcția shallow
încarcă în memorie doar componenta rădăcină, în timp ce mount
încarcă arborele DOM complet.
Vom combina Enzyme și Jest pentru a monta o componentă React.js și vom rula aserțiuni peste ea.
Configurarea mediului nostru
Puteți arunca o privire la acest depozit, care are configurația de bază pentru a rula acest exemplu.
Folosim următoarele versiuni:
{ "react": "16.0.0", "enzyme": "^2.9.1", "jest": "^21.2.1", "jest-cli": "^21.2.1", "babel-jest": "^21.2.0" }
Crearea componentei React.js folosind TDD
Primul pas este să creați un test care eșuează, care va încerca să redea o componentă React.js folosind funcția superficială a enzimei.
// MyComponent.test.js import React from 'react'; import { shallow } from 'enzyme'; import MyComponent from './MyComponent'; describe("MyComponent", () => { it("should render my component", () => { const wrapper = shallow(<MyComponent />); }); });
După rularea testului, obținem următoarea eroare:
ReferenceError: MyComponent is not defined.
Apoi creăm componenta care oferă sintaxa de bază pentru a trece testul.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div />; } }
În pasul următor, ne vom asigura că componenta noastră redă un aspect UI predefinit folosind funcția toMatchSnapshot
de la Jest.
După apelarea acestei metode, Jest creează automat un fișier instantaneu numit [testFileName].snap
, căruia i se adaugă folderul __snapshots__
.
Acest fișier reprezintă aspectul UI pe care îl așteptăm de la randarea componentelor noastre.
Cu toate acestea, având în vedere că încercăm să facem TDD pur , ar trebui să creăm mai întâi acest fișier și apoi să apelăm funcția toMatchSnapshot
pentru a face testul să eșueze.
Acest lucru poate suna puțin confuz, având în vedere că nu știm ce format folosește Jest pentru a reprezenta acest aspect.
Este posibil să fiți tentat să executați mai întâi funcția toMatchSnapshot
și să vedeți rezultatul în fișierul snapshot, iar aceasta este o opțiune validă. Cu toate acestea, dacă vrem cu adevărat să folosim TDD pur , trebuie să învățăm cum sunt structurate fișierele instantanee.
Fișierul instantaneu conține un aspect care se potrivește cu numele testului. Aceasta înseamnă că dacă testul nostru are această formă:
desc("ComponentA" () => { it("should do something", () => { … } });
Ar trebui să specificăm acest lucru în secțiunea de exporturi: Component A should do something 1
.
Puteți citi mai multe despre testarea instantanee aici.
Deci, mai întâi creăm fișierul MyComponent.test.js.snap
.
//__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input type="text" /> </div>, ] `;
Apoi, creăm testul unitar care va verifica dacă instantaneul se potrivește cu elementele secundare componente.

// MyComponent.test.js ... it("should render initial layout", () => { // when const component = shallow(<MyComponent />); // then expect(component.getElements()).toMatchSnapshot(); }); ...
Putem considera components.getElements
ca rezultat al metodei de randare.
Trecem aceste elemente la metoda expect
pentru a rula verificarea pe fișierul snapshot.
După executarea testului obținem următoarea eroare:
Received value does not match stored snapshot 1. Expected: - Array [ <div> <input type="text” /> </div>, ] Actual: + Array []
Jest ne spune că rezultatul de la component.getElements
nu se potrivește cu instantaneul. Deci, facem acest test să treacă prin adăugarea elementului de intrare în MyComponent
.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input type="text" /></div>; } }
Următorul pas este să adăugați funcționalitate la input
prin executarea unei funcții atunci când valoarea acesteia se schimbă. Facem acest lucru specificând o funcție în prop onChange
.
Mai întâi trebuie să schimbăm instantaneul pentru ca testul să eșueze.
//__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input onChange={[Function]} type="text" /> </div>, ] `;
Un dezavantaj al modificării mai întâi a instantaneului este că ordinea elementelor de recuzită (sau a atributelor) este importantă.
Jest va sorta alfabetic elementele de recuzită primite în funcția de expect
înainte de a le verifica în raport cu instantaneul. Deci, ar trebui să le specificăm în această ordine.
După executarea testului obținem următoarea eroare:
Received value does not match stored snapshot 1. Expected: - Array [ <div> onChange={[Function]} <input type="text”/> </div>, ] Actual: + Array [ <div> <input type=”text” /> </div>, ]
Pentru a face acest test să treacă, putem pur și simplu să furnizăm o funcție goală pentru onChange
.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={() => {}} type="text" /></div>; } }
Apoi, ne asigurăm că starea componentei se schimbă după ce evenimentul onChange
este expediat.
Pentru a face acest lucru, creăm un nou test unitar care va apela funcția onChange
în intrare prin trecerea unui eveniment pentru a imita un eveniment real în UI.
Apoi, verificăm că starea componentei conține o cheie numită input
.
// MyComponent.test.js ... it("should create an entry in component state", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toBeDefined(); });
Acum obținem următoarea eroare.
Expected value to be defined, instead received undefined
Aceasta indică faptul că componenta nu are o proprietate în starea numită input
.
Facem testul să treacă setând această intrare în starea componentei.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => {this.setState({input: ''})}} type="text" /></div>; } }
Apoi, trebuie să ne asigurăm că o valoare este setată în noua intrare de stat. Vom obține această valoare de la eveniment.
Deci, să creăm un test care să se asigure că starea conține această valoare.
// MyComponent.test.js ... it("should create an entry in component state with the event value", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toEqual('myValue'); }); ~~~ Not surprisingly, we get the following error. ~~ Expected value to equal: "myValue" Received: ""
În cele din urmă, facem acest test să treacă prin obținerea valorii din eveniment și setând-o ca valoare de intrare.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => { this.setState({input: event.target.value})}} type="text" /></div>; } }
După ce ne asigurăm că toate testele trec, ne putem refactoriza codul.
Putem extrage funcția transmisă în prop onChange
la o nouă funcție numită updateState
.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { updateState(event) { this.setState({ input: event.target.value }); } render() { return <div><input onChange={this.updateState.bind(this)} type="text" /></div>; } }
Acum avem o componentă simplă React.js creată folosind TDD.
rezumat
În acest exemplu, am încercat să folosim TDD pur , urmând fiecare pas scriind cel mai mic cod posibil pentru a eșua și a trece testele.
Unii dintre pași pot părea inutili și putem fi tentați să-i sărim. Cu toate acestea, ori de câte ori omitem orice pas, vom ajunge să folosim o versiune mai puțin pură a TDD.
Utilizarea unui proces TDD mai puțin strict este, de asemenea, validă și poate funcționa bine.
Recomandarea mea pentru tine este sa eviti sa sari peste orice pas si sa nu te simti rau daca ti se pare greu. TDD este o tehnică care nu este ușor de stăpânit, dar cu siguranță merită făcută.
Dacă sunteți interesat să aflați mai multe despre TDD și dezvoltarea aferentă determinată de comportament (BDD), citiți Șeful dvs. nu va aprecia TDD de colegul Toptaler Ryan Wilcox.