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.
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:
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.
Dit zijn de tests die we in de laatste zelfstudie hebben geschreven:
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.
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.
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 ()))
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 testenDe unit test zal proberen de volgende aannames te doen gelden:
Hier is de kale structuur van de test. De eerste beforeEach ()
slaat de productgegevens op in een variabele en de tweede mount de component.
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.
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.
zou de callback-functie moeten aanroepen.Laten we een maken beforeEach ()
functie die nepproductgegevens opvult voor onze tests.
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.
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);)
Vervolgens gaan we het testen ProductContainer
component. Het heeft een status, een levenscyclushaak en een klassemethode. Hier zijn de beweringen die moeten worden geverifieerd:
componentDidMount
wordt precies één keer genoemd.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:
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:
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.