Componenten testen in Reageren met Jest en Enzyme

Dit is het tweede deel van de serie over het testen van componenten in React. Als je al ervaring hebt met Jest, kun je doorgaan en de GitHub-code als uitgangspunt gebruiken. 

In het vorige artikel hebben we de basisprincipes en ideeën achter testgedreven ontwikkeling behandeld. We hebben ook de omgeving en de hulpmiddelen ingesteld die nodig zijn voor het uitvoeren van tests in React. De toolset omvatte Jest, ReactTestUtils, Enzyme en react-test-renderer. 

We hebben toen een aantal tests voor een demotoepassing met ReactTestUtils geschreven en de tekortkomingen ervan ontdekt in vergelijking met een meer robuuste bibliotheek zoals Enzyme.

In dit bericht krijgen we een beter begrip van het testen van componenten in React door meer praktische en realistische tests te schrijven. Je kunt naar GitHub gaan en mijn repository klonen voordat je begint.

Aan de slag met de Enzyme API

Enzyme.js is een open-sourcebibliotheek die wordt onderhouden door Airbnb en is een geweldige bron voor React-ontwikkelaars. Het gebruikt de ReactTestUtils API eronder, maar in tegenstelling tot ReactTestUtils biedt Enzyme een high-level API en een gemakkelijk te begrijpen syntaxis. Installeer Enzyme als je dat nog niet hebt gedaan.

De Enzyme API exporteert drie soorten weergaveopties:

  1. ondiepe weergave
  2. volledige DOM-weergave
  3. statische weergave

Ondiepe weergave wordt gebruikt om een ​​bepaald onderdeel geïsoleerd te maken. De onderliggende componenten worden niet weergegeven en daarom kunt u hun gedrag niet bevestigen. Als je je gaat concentreren op unit tests, zul je dit geweldig vinden. U kunt een component als deze ondiep renderen:

import shallow van 'enzyme'; importeer ProductHeader van './ProductHeader'; // Meer concreet voorbeeld hieronder. const component = ondiep (); 

Volledige DOM-weergave genereert een virtuele DOM van de component met behulp van een bibliotheek genaamd jsdom. U kunt deze functie gebruiken door de Ondiep() methode met mount () in het bovenstaande voorbeeld. Het voor de hand liggende voordeel is dat u de onderliggende componenten ook kunt weergeven. Als je het gedrag van een component bij zijn kinderen wilt testen, zou je dit moeten gebruiken. 

Statische weergave wordt gebruikt om componenten te laten reageren op statische HTML. Het is geïmplementeerd met behulp van een bibliotheek genaamd Cheerio en je kunt er meer over lezen in de documenten. 

Onze vorige testen opnieuw bekijken

Dit zijn de tests die we in de laatste zelfstudie hebben geschreven:

src / components / __ testen __ / ProductHeader.test.js

import ReactTestUtils van 'react-dom / test-utils'; // ES6 beschrijven ('ProductHeader Component', () => it ('has a h2 tag', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithTag (component, 'h2'); ); it ('heeft een titelklasse', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithClass (component, 'title'); ))

De eerste test controleert of de ProducerHeader component heeft een

tag en de tweede vindt of een CSS-klasse is genoemd titel. De code is moeilijk te lezen en te begrijpen. 

Hier zijn de tests herschreven met behulp van Enzyme.

src / components / __ testen __ / ProductHeader.test.js

import shallow van 'enzyme' beschrijven ('ProductHeader Component', () => it ('has a h2 tag', () => const component = shallow (); var node = component.find ('h2'); verwacht (node.length) .toEqual (1); ); it ('heeft een titelklasse', () => const component = shallow (); var node = component.find ('h2'); verwachten (node.hasClass ( 'title')) toBeTruthy (.); ))

Ten eerste heb ik een ondiepe gegenereerde DOM gemaakt van de  component gebruik Ondiep() en opgeslagen in een variabele. Vervolgens gebruikte ik de .vind() methode om een ​​knooppunt met de tag 'h2' te vinden. Het vraagt ​​de DOM om te zien of er een overeenkomst is. Omdat er maar één exemplaar van het knooppunt is, kunnen we dat veilig aannemen node.length zal gelijk zijn aan 1.

De tweede test lijkt veel op de eerste. De hasClass ( 'title') methode retourneert of het huidige knooppunt een heeft naam van de klasse prop met waarde 'titel'. We kunnen de waarachtigheid verifiëren met behulp van toBeTruthy ().  

Voer de tests uit met garen test, en beide tests moeten slagen. 

Goed gedaan! Nu is het tijd om de code te refactoren. Dit is belangrijk vanuit het perspectief van een tester, omdat leesbare tests gemakkelijker te onderhouden zijn. In de bovenstaande tests zijn de eerste twee regels identiek voor beide tests. Je kunt ze refactiveren door een a te gebruiken beforeEach () functie. Zoals de naam al doet vermoeden, de beforeEach functie wordt eenmaal aangeroepen voordat elke specificatie in een beschrijvingsblok wordt uitgevoerd. 

U kunt een pijlfunctie doorgeven aan beforeEach () zoals dit.

src / components / __ testen __ / ProductHeader.test.js

import shallow van 'enzyme' beschrijven ('ProductHeader Component', () => laat component, knooppunt // Jest beforeEach () beforeEach (((= = component = shallow ())) beforeEach ((((=) = node = component.find ('h2'))) it ('has a h2 tag', () => expect (node) .toBeTruthy ()); it ('heeft een titelklasse', () => verwacht (node.hasClass ('title')). toBeTruthy ()))

Schrijven van eenheidstesten met Jest en enzym

Laten we een paar eenheidstests schrijven voor de Productdetails component. Het is een presentatiecomponent die de details van elk afzonderlijk product weergeeft. 

We gaan het gedeelte dat gemarkeerd is testen

De unit test zal proberen de volgende aannames te doen gelden:

  • Het onderdeel bestaat en de rekwisieten worden doorgegeven.
  • De rekwisieten zoals de naam, beschrijving en beschikbaarheid van het product worden weergegeven.
  • Een foutmelding wordt weergegeven als de rekwisieten leeg zijn.

Hier is de kale structuur van de test. De eerste beforeEach () slaat de productgegevens op in een variabele en de tweede mount de component.

src / components / __ testen __ / ProductDetails.test.js

beschrijven ("ProductDetails component", () => var component, product; beforeEach (() => product = id: 1, naam: 'NIKE Liteforce Blue Sneakers', beschrijving: 'Lorem ipsum.', status: 'Beschikbaar';) beforeEach (() => component = mount (); ) het ('test # 1', () => ))

De eerste test is eenvoudig:

it ('should exist', () => expect (component) .toBeTruthy (); expect (component.props (). product) .toEqual (product);)

Hier gebruiken we de rekwisieten() methode die handig is om de rekwisieten van een component te krijgen.

Voor de tweede test kunt u elementen opvragen op basis van hun klassenamen en vervolgens controleren of de naam, de beschrijving, etc. van het product deel uitmaken van de elementen van dat element. innerText

 it ('zou productgegevens moeten weergeven wanneer rekwisieten worden gepasseerd', () => let title = component.find ('. product-title'); expect (title.text ()). toEqual (product.name); let description = component.find ('. product-description'); expect (description.text ()). toEqual (product.description);) 

De tekst() methode is in dit geval met name handig om de interne tekst van een element op te halen. Probeer een verwachting te schrijven voor de product status() en kijk of alle tests slagen.

Voor de laatste test gaan we de Productdetails component zonder enige rekwisieten. Dan gaan we op zoek naar een klasse met de naam '.product-error' en controleren of deze de tekst bevat: 'Sorry, het product bestaat niet'.

 it ('zou een fout moeten tonen als rekwisieten niet gepasseerd zijn', () => / * component zonder rekwisieten * / component = mount (); let node = component.find ('. product-error'); verwacht (node.text ()). toEqual ('Sorry, het product bestaat niet'); ) 

Dat is het. We hebben de. Met succes getest component afzonderlijk. Tests van dit type worden eenheidstests genoemd.

Callbacks testen met behulp van stubs en spionnen

We hebben net geleerd hoe je rekwisieten test. Maar om een ​​onderdeel echt geïsoleerd te testen, moet u ook de callback-functies testen. In deze sectie zullen we tests schrijven voor de Product lijst component en creëer onderweg stubs voor callback-functies. Dit zijn de veronderstellingen die we moeten beweren.

  1. Het aantal vermelde producten moet gelijk zijn aan het aantal objecten dat het onderdeel als rekwisieten ontvangt.
  2. Klik op zou de callback-functie moeten aanroepen.

Laten we een maken beforeEach () functie die nepproductgegevens opvult voor onze tests.

src / components / __ testen __ / ProductList.test.js

 beforeEach (() => productData = [id: 1, naam: 'NIKE Liteforce Blue Sneakers', beschrijving: 'Lorem ipsu.', status: 'Beschikbaar', // Weggelaten voor beknoptheid])

Laten we nu onze component in een andere monteren beforeEach () blok.

beforeEach (() => handleProductClick = jest.fn (); component = mount (  ); )

De Product lijst ontvangt de productgegevens via rekwisieten. Daarnaast ontvangt het een callback van de ouder. Hoewel je tests kunt schrijven voor de callback-functie van de ouder, is dat geen goed idee als je je wilt vasthouden aan unit-tests. Aangezien de callback-functie tot de bovenliggende component behoort, zal het incorporeren van de logica van de ouder de testen gecompliceerd maken. In plaats daarvan gaan we een stub-functie maken.

Wat is een Stub? 

Een stub is een dummyfunctie die zich voordoet als een andere functie. Hiermee kunt u een onderdeel onafhankelijk testen zonder onderdelen van bovenliggende of onderliggende elementen te importeren. In het bovenstaande voorbeeld hebben we een stub-functie gemaakt met de naam handleProductClick door aan te roepen jest.fn ()

Nu moeten we gewoon de alle vinden elementen in de DOM en simuleer een klik op de eerste knooppunt. Nadat er op is geklikt, controleren we of handleProductClick () werd aangeroepen. Zo ja, dan is het redelijk om te zeggen dat onze logica werkt zoals verwacht.

it ('zou selectProduct moeten bellen wanneer erop wordt geklikt', () => const firstLink = component.find ('a'). first (); firstLink.simulate ('click'); expect (handleProductClick.mock.calls.length) .toEqual (1);))

Met Enzyme kunt u gemakkelijk gebruikersacties simuleren, zoals klikken met simuleren() methode. handlerProductClick.mock.calls.length geeft het aantal keren terug dat de mock-functie is aangeroepen. We verwachten dat het gelijk is aan 1.

De andere test is relatief eenvoudig. U kunt de vind() methode om alles op te halen knooppunten in de DOM. Het aantal knooppunten moeten gelijk zijn aan de lengte van de productData-array die we eerder hebben gemaakt. 

 it ('zou alle productitems moeten weergeven', () => let links = component.find ('a'); expect (links.length) .toEqual (productData.length);) 

De status van de component, LifeCycleHook en methode testen

Vervolgens gaan we het testen ProductContainer component. Het heeft een status, een levenscyclushaak en een klassemethode. Hier zijn de beweringen die moeten worden geverifieerd:

  1. componentDidMount wordt precies één keer genoemd.
  2. De status van de component wordt ingevuld nadat de component is aangekoppeld.
  3. De handleProductClick () methode moet de status bijwerken wanneer een product-ID wordt doorgegeven als een argument.

Om te controleren of componentDidMount werd genoemd, we gaan het bespioneren. In tegenstelling tot een stub, wordt een spion gebruikt wanneer u een bestaande functie moet testen. Nadat de spion is ingesteld, kunt u beweringen schrijven om te bevestigen of de functie is aangeroepen.

U kunt een functie als volgt bespioneren:

src / components / __ testen __ / ProductContainer.test.js

 it ('zou componentDidMount eenmaal moeten aanroepen', () => componentDidMountSpy = spyOn (ProductContainer.prototype, 'componentDidMount'); // Te voltooien);

De eerste parameter voor jest.spyOn is een object dat het prototype definieert van de klasse die we bespioneren. De tweede is de naam van de methode die we willen bespioneren. 

Geef nu de component weer en maak een bewering om te controleren of spion werd gebeld.

 component = ondiep (); verwacht (componentDidMountSpy) .toHaveBeenCalledTimes (1);

Om na te gaan of de status van de component wordt ingevuld nadat de component is aangekoppeld, kunnen we Enzyme's gebruiken staat() methode om alles in de staat op te halen. 

it ('zou de staat moeten vullen', () => component = shallow (); verwacht (component.state (). productList.length) .toEqual (4))

De derde is een beetje lastig. We moeten dat verifiëren handleProductClick werkt zoals verwacht. Als u naar de code gaat, ziet u dat de handleProductClick () methode neemt een product-ID als invoer en wordt vervolgens bijgewerkt this.state.selectedProduct met de details van dat product. 

Om dit te testen, moeten we de methode van het onderdeel aanroepen, en dat kunt u ook doen door te bellen component.instance (). handleProductClick (). We geven een voorbeeldproduct-ID door. In het onderstaande voorbeeld gebruiken we de id van het eerste product. Vervolgens kunnen we testen of de status is bijgewerkt om te bevestigen dat de bewering klopt. Hier is de hele code:

 it ('zou een werkmethode moeten hebben genaamd handleProductClick', () => let firstProduct = productData [0] .id; component = shallow (); . Component.instance () handleProductClick (firstProduct); verwacht (component.state (). selectedProduct) .toEqual (productData [0]); ) 

We hebben 10 tests geschreven en als alles goed gaat, is dit wat u zou moeten zien:

Samenvatting

Phew! We hebben bijna alles besproken wat u moet weten om te beginnen met het schrijven van tests in React met behulp van Jest en Enzyme. Dit is misschien een goed moment om naar de Enzyme-website te gaan om hun API beter te bekijken.

Wat vindt u van het schrijven van tests in React? Ik hoor ze graag in de comments.