Testing av komponenter i reaktion med användning av jäst och enzym

Detta är den andra delen av serien om testkomponenter i reaktion. Om du har tidigare erfarenhet av Jest kan du gå vidare och använda GitHub-koden som utgångspunkt. 

I den föregående artikeln täckte vi de grundläggande principerna och idéerna bakom testdriven utveckling. Vi etablerar också miljön och de verktyg som krävs för att köra test i React. Verktygssatsen inkluderade Jest, ReactTestUtils, Enzyme och React-Test-renderer. 

Vi skrev sedan ett par test för en demo-applikation med ReactTestUtils och upptäckte sina brister jämfört med ett mer robust bibliotek som Enzyme.

I det här inlägget får vi en djupare förståelse för testning av komponenter i React genom att skriva mer praktiska och realistiska tester. Du kan gå till GitHub och klona min repo innan du börjar.

Komma igång med enzymet API

Enzyme.js är ett open source-bibliotek som underhålls av Airbnb, och det är en bra resurs för React-utvecklare. Det använder ReactTestUtils API nedan, men i motsats till ReactTestUtils erbjuder Enzyme en hög API och en lättfattad syntax. Installera Enzyme om du inte redan har det.

Enzyme API exporterar tre typer av återgivningsalternativ:

  1. grunt rendering
  2. full DOM-rendering
  3. statisk rendering

Grunt rendering används för att göra en viss komponent i isolation. Barnkomponenterna kommer inte att bli gjorda, och därmed kommer du inte att kunna hävda sitt beteende. Om du kommer att fokusera på enhetstest kommer du att älska detta. Du kan grunt göra en komponent så här:

importera grunt från "enzym"; importera ProductHeader från './ProductHeader'; // Mer konkret exempel nedan. const komponent = grunt (); 

Full DOM-rendering genererar en virtuell DOM av komponenten med hjälp av ett bibliotek som heter jsdom. Du kan använda denna funktion genom att ersätta grund() metod med montera() i ovanstående exempel. Den uppenbara fördelen är att du kan göra barnkomponenterna också. Om du vill testa beteendet hos en komponent med sina barn ska du använda det här. 

Statisk återgivning används för att göra reaktionskomponenter till statisk HTML. Det implementeras med ett bibliotek som heter Cheerio, och du kan läsa mer om det i dokumenten. 

Revidering av våra tidigare test

Här är de tester som vi skrev i den sista handledningen:

src / komponenter / __ tester __ / ProductHeader.test.js

Importera ReactTestUtils från "React-dom / test-utils"; // ES6 beskriver ('ProductHeader Component', () => it ('har en h2 tag', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithTag (komponent, 'h2'); ); det ('har en titel klass', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithClass (komponent, "titel"); ))

Det första testet kontrollerar om ProducerHeader komponenten har en

tagg och den andra visar om den har en CSS-klass som heter titel. Koden är svår att läsa och förstå. 

Här är testen omskrivna med Enzyme.

src / komponenter / __ tester __ / ProductHeader.test.js

import shallow från "enzym" beskriver ('ProductHeader Component', () => it ('har en h2-tag', () => const komponent = grunt); var node = component.find ('h2'); förväntar (node.length) .toEqual (1); ); det ('har en titel klass', () => const komponent = grunt (); var node = component.find ('h2'); förväntar (node.hasClass ( 'title')) toBeTruthy ().; ))

Först skapade jag en grunt renad DOM av  komponent med grund() och lagrade den i en variabel. Sedan brukade jag .hitta() metod för att hitta en nod med tagg 'h2'. Det frågar DOM för att se om det finns en match. Eftersom det bara finns en instans av noden kan vi säkert anta det node.length kommer att vara lika med 1.

Det andra testet är mycket lik det första. De hasClass ( 'title') Metoden returnerar om nuvarande nod har en klassnamn prop med värde "titel". Vi kan verifiera sanningen genom att använda toBeTruthy ().  

Kör testen med garntest, och båda provningarna ska passera. 

Bra gjort! Nu är det dags att refactor koden. Detta är viktigt ur testarens perspektiv, eftersom läsbara tester är enklare att underhålla. I ovanstående tester är de två första linjerna identiska för båda proven. Du kan refactor dem genom att använda en beforeEach () fungera. Som namnet antyder, beforeEach funktion kallas en gång före varje spec i ett beskrivningsblock exekveras. 

Du kan skicka en pilfunktion till beforeEach () så här.

src / komponenter / __ tester __ / ProductHeader.test.js

import shallow från "enzym" beskriva ("ProductHeader Component", () => låt komponent, node; // Jest beforeEach () beforeEach (() => komponent = grunt))) beforeEach (() => node = component.find ('h2'))) det ('har en h2 tag', () => expect (node) .toBeTruthy ()); den ('har en titel klass', () => förvänta (node.hasClass ('title')). toBeTruthy ()))

Skrivningsenhetstest med jäst och enzym

Låt oss skriva några enhetstester för Produktinformation komponent. Det är en presentationskomponent som visar detaljerna för varje enskild produkt. 

Vi ska testa det avsnitt som är markerat

Enhetstesten kommer att försöka hävda följande antaganden:

  • Komponenten finns och rekvisita kommer att gå ner.
  • Rekvisita som produktens namn, beskrivning och tillgänglighet visas.
  • Ett felmeddelande visas när rekvisita är tomma.

Här är testets enda benstruktur. Den första beforeEach () lagrar produktdata i en variabel och den andra monterar komponenten.

src / komponenter / __ tester __ / ProductDetails.test.js

beskriva ("ProductDetails component", () => var komponent, produkt; beforeEach (() => product = id: 1, namn: 'NIKE Liteforce Blue Sneakers', beskrivning: 'Lorem ipsum.', status: 'Tillgänglig';) föreEach (() => komponent = montera (); ) det ("test # 1", () => ))

Det första testet är enkelt:

den ("ska existera", () => förvänta (komponent). toBeTruthy (); expect (component.props () .product) .toEqual (product);)

Här använder vi rekvisita() metod som är praktisk för att få rekvisita av en komponent.

För det andra testet kan du fråga element med sina klassnamn och kontrollera om produktens namn, beskrivning etc. är en del av det här elementets innerText

 den ('ska visa produktdata när rekvisita skickas', () => let title = component.find ('.product-title'); förvänta (title.text ()) .Equal (product.name) description = component.find ('. produktbeskrivning'); förvänta (description.text ()) .Equal (product.description);) 

De text() Metoden är särskilt användbar i detta fall för att hämta den inre texten av ett element. Försök skriva en förväntan för product.status () och se om alla tester passerar.

För det slutliga testet ska vi montera Produktinformation komponent utan några rekvisita. Då ska vi leta efter en klass med namnet ".product-error" och kontrollera om det innehåller texten "Tyvärr, produkten existerar inte".

 det ("ska visa ett fel när rekvisita inte passeras", () => / * komponent utan rekvisita * / komponent = mount (); låt nod = komponent.find ('. produktfel'); förvänta sig (node.text ()). toEqual ("Ledsen. Produkten existerar inte"); ) 

Det är allt. Vi har testat testet framgångsrikt komponent isolerat. Test av denna typ kallas enhetstester.

Testa återuppringningar med hjälp av stubbar och spioner

Vi lärde oss bara hur man testar rekvisita. Men för att verkligen prova en komponent i isolation måste du också testa återuppringningsfunktionerna. I det här avsnittet skriver vi tester för Produktlista komponent och skapa stubbar för återuppringningsfunktioner längs vägen. Här är de antaganden som vi behöver hävda.

  1. Antalet produkter som anges ska motsvara antalet objekt som komponenten får som rekvisita.
  2. Klicka på bör anropa återuppringningsfunktionen.

Låt oss skapa en beforeEach () funktion som fyller i mocka produktdata för våra test.

src / komponenter / __ tester __ / ProductList.test.js

 beforeEach () => productData = [id: 1, namn: 'NIKE Liteforce Blue Sneakers', beskrivning: 'Lorem ipsu.', status: 'Tillgänglig', // Utelämnad för korthet])

Nu, låt oss montera vår komponent i en annan beforeEach () blockera.

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

De Produktlista mottar produktdata genom rekvisita. Utöver det får den återkallelse från föräldern. Även om du kunde skriva test för föräldrars återuppringningsfunktion, är det inte en bra idé om ditt mål är att hålla sig till enhetsprov. Eftersom återuppringningsfunktionen tillhör moderkomponenten kommer inlämning av föräldrarnas logik att göra testerna komplicerade. I stället ska vi skapa en stubfunktion.

Vad är en Stub? 

En stub är en dummyfunktion som låtsas vara en annan funktion. Detta gör det möjligt att självständigt testa en komponent utan att importera föräldrar eller barnkomponenter. I exemplet ovan skapade vi en stubfunktion som heter handleProductClick genom att åberopa jest.fn ()

Nu behöver vi bara hitta alla element i DOM och simulera ett klick på det första nod. Efter att ha blivit klickat kontrollerar vi om handleProductClick () åberopades. Om ja, det är rimligt att säga att vår logik fungerar som förväntat.

den ('ska ringa selectProduct när den klickas', () => const firstLink = component.find ('a'). först (); firstLink.simulate ('click'); förvänta (hantera produktClick.mock.calls.length) . till lika (1);))

Enzym låter dig enkelt simulera användaråtgärder som klick med simulera() metod. handlerProductClick.mock.calls.length returnerar antalet gånger som mock-funktionen heter. Vi förväntar oss att det är lika med 1.

Det andra testet är relativt enkelt. Du kan använda hitta() metod för att hämta allt noder i DOM. Antalet noder borde vara lika med längden på produktdatabasen som vi skapade tidigare. 

 den ("ska visa alla produktobjekt", () => let links = component.find ('a'); förvänta (links.length) .toEqual (productData.length);) 

Testa komponentens tillstånd, LifeCycleHook och Method

Därefter ska vi testa ProductContainer komponent. Den har en stat, en livscykelkrok och en klassmetod. Här är de påståenden som måste verifieras:

  1. componentDidMount kallas exakt en gång.
  2. Komponentets tillstånd är befolket efter komponentfästena.
  3. De handleProductClick () Metoden ska uppdatera staten när ett produkt-id skickas in som ett argument.

För att kontrollera om componentDidMount kallades, vi ska spionera på den. Till skillnad från en stub används en spion när du behöver testa en befintlig funktion. När spionen är inställd kan du skriva påståenden för att bekräfta om funktionen heter.

Du kan spionera på en funktion enligt följande:

src / komponenter / __ tester __ / ProductContainer.test.js

 det ('ska kalla componentDidMount once', () => componentDidMountSpy = spyOn (ProductContainer.prototype, 'componentDidMount'); // För att vara klar);

Den första parametern till jest.spyOn är ett objekt som definierar prototypen av den klass som vi spionerar på. Den andra är namnet på den metod som vi vill spionera. 

Nu gör komponenten och skapa ett påstående för att kontrollera om spion heter.

 komponent = grunt (); förvänta sig (componentDidMountSpy) .toHaveBeenCalledTimes (1);

För att kontrollera att komponentens tillstånd är befolket efter komponentmonteringen, kan vi använda Enzyme stat() metod för att hämta allt i staten. 

det ("bör fylla staten", () => komponent = grunt (); förvänta (component.state (). productList.length) .toEqual (4))

Den tredje är lite knepig. Vi måste verifiera det handleProductClick arbetar som förväntat. Om du går över till koden ser du att handleProductClick () Metoden tar ett produkt-ID som inmatning, och sedan uppdateringar this.state.selectedProduct med detaljerna om den produkten. 

För att testa detta måste vi anropa komponentens metod, och du kan faktiskt göra det genom att ringa component.instance (). handleProductClick (). Vi skickar in ett provprodukt-id. I exemplet nedan använder vi ID för den första produkten. Då kan vi testa om staten uppdaterades för att bekräfta att påståendet är sant. Här är hela koden:

 det ('ska ha en arbetsmetod som kallas handleProductClick', () => låt firstProduct = productData [0] .id; komponent = grunt); . Component.instance () handleProductClick (first); förvänta (component.state (). selectedProduct) .toEqual (productData [0]); ) 

Vi har skrivit 10 tester, och om allt går bra är det här du bör se:

Sammanfattning

Phew! Vi har täckt nästan allt som du behöver veta för att komma igång med skrivprov i React using Jest and Enzyme. Nu kan det vara en bra tid att gå vidare till Enzyms webbplats för att få en djupare titt på deras API.

Vad är dina tankar om skrivprov i React? Jag skulle gärna höra dem i kommentarerna.