Test dei componenti in React utilizzando Jest ed Enzyme

Questa è la seconda parte della serie su Testing Components in React. Se hai precedenti esperienze con Jest, puoi saltare avanti e utilizzare il codice GitHub come punto di partenza. 

Nell'articolo precedente, abbiamo trattato i principi e le idee di base dello sviluppo basato sui test. Abbiamo anche creato l'ambiente e gli strumenti necessari per eseguire i test in React. Il set di strumenti include Jest, ReactTestUtils, Enzyme e il renderer di test reattivo. 

Abbiamo quindi scritto un paio di test per un'applicazione demo utilizzando ReactTestUtils e scoperto i suoi difetti rispetto a una libreria più robusta come Enzyme.

In questo post, otterremo una comprensione più approfondita dei componenti di test in React scrivendo test più pratici e realistici. Puoi andare su GitHub e clonare il mio repository prima di iniziare.

Iniziare con l'API Enzyme

Enzyme.js è una libreria open-source gestita da Airbnb, ed è una grande risorsa per gli sviluppatori di React. Utilizza l'API ReactTestUtils sottostante, ma a differenza di ReactTestUtils, Enzyme offre un'API di alto livello e sintassi di facile comprensione. Installa Enzyme se non l'hai già fatto.

L'API Enzyme esporta tre tipi di opzioni di rendering:

  1. resa superficiale
  2. rendering DOM completo
  3. rendering statico

Rendering superficiale è usato per rendere un particolare componente in isolamento. I componenti figlio non saranno resi e quindi non sarai in grado di far valere il loro comportamento. Se hai intenzione di concentrarti sui test unitari, questo ti piacerà. Puoi rendere superficiale un componente come questo:

import shallow da 'enzima'; import ProductHeader da './ProductHeader'; // Esempio più concreto di seguito. const component = shallow (); 

Rendering DOM completo genera un DOM virtuale del componente con l'aiuto di una libreria chiamata jsdom. È possibile avvalersi di questa funzione sostituendo il poco profonde () metodo con montare() nell'esempio sopra. L'ovvio vantaggio è che puoi rendere anche i componenti figlio. Se vuoi testare il comportamento di un componente con i suoi figli, dovresti usarlo. 

Rendering statico è usato per rendere i componenti che reagiscono all'HTTP statico. È implementato utilizzando una libreria denominata Cheerio e puoi leggerne altre nei documenti. 

Rivisitare i nostri test precedenti

Ecco i test che abbiamo scritto nell'ultimo tutorial:

src / componenti / __ test __ / ProductHeader.test.js

importare ReactTestUtils da 'react-dom / test-utils'; // ES6 descrive ('ProductHeader Component', () => it ('ha un tag h2', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithTag (componente, 'h2'); ); ('has a title class', () => const component = ReactTestUtils .renderIntoDocument (); var node = ReactTestUtils .findRenderedDOMComponentWithClass (componente, 'titolo'); ))

Il primo test verifica se il ProducerHeader il componente ha un

tag, e il secondo trova se ha una classe CSS chiamata titolo. Il codice è difficile da leggere e capire. 

Ecco i test riscritti usando Enzyme.

src / componenti / __ test __ / ProductHeader.test.js

import shallow da 'enzyme' describe ('ProductHeader Component', () => it ('ha un tag h2', () => const component = shallow (); var node = component.find ('h2'); si aspettano (node.length) .toEqual (1); ); ('has a title class', () => const component = shallow (); var node = component.find ('h2'); si aspettano (node.hasClass ( 'title')) toBeTruthy ().; ))

Per prima cosa, ho creato un DOM con renderizzazione superficiale  componente che utilizza poco profonde () e memorizzato in una variabile. Quindi, ho usato il .trova() metodo per trovare un nodo con tag 'h2'. Si interroga il DOM per vedere se c'è una corrispondenza. Poiché esiste una sola istanza del nodo, possiamo tranquillamente supporre che node.length sarà uguale a 1.

Il secondo test è molto simile al primo. Il hasClass ( 'title') il metodo restituisce se il nodo corrente ha a nome della classe prop con valore 'titolo'. Possiamo verificare la veridicità usando toBeTruthy ().  

Esegui i test usando test del filato, ed entrambi i test dovrebbero passare. 

Molto bene! Ora è il momento di rifattorizzare il codice. Questo è importante dal punto di vista del tester perché i test leggibili sono più facili da mantenere. Nei test precedenti, le prime due righe sono identiche per entrambi i test. Puoi refactoring usando a beforeeach () funzione. Come suggerisce il nome, il beforeeach la funzione viene chiamata una volta prima che ciascuna specifica in un blocco descrittivo venga eseguita. 

È possibile passare una funzione freccia a beforeeach () come questo.

src / componenti / __ test __ / ProductHeader.test.js

import shallow da 'enzyme' describe ('Componente ProductHeader', () => lascia componente, nodo; // Jest beforeEach () beforeEach ((() => component = shallow ())) beforeEach ((() => node = component.find ('h2'))) it ('ha un tag h2', () => expect (node) .toBeTruthy ()); ('has a title class', () => expect (node.hasClass ('title')). toBeTruthy ()))

Scrittura dei test unitari con jest ed enzima

Scriviamo alcuni test unitari per il Dettagli del prodotto componente. È un componente di presentazione che mostra i dettagli di ogni singolo prodotto. 

Stiamo andando a testare la sezione che è evidenziata

Il test unitario cercherà di affermare le seguenti ipotesi:

  • Il componente esiste e gli oggetti di scena vengono tramandati.
  • Vengono visualizzati gli oggetti di scena come il nome del prodotto, la descrizione e la disponibilità.
  • Un messaggio di errore viene visualizzato quando gli oggetti di scena sono vuoti.

Ecco la struttura a ossa nude del test. Il primo beforeeach () memorizza i dati del prodotto in una variabile e il secondo monta il componente.

src / componenti / __ test __ / ProductDetails.test.js

descrivere ("Componente ProductDetails", () => var component, product; beforeEach (() => product = id: 1, nome: 'NIKE Liteforce Blue Sneakers', descrizione: 'Lorem ipsum.', stato: 'Disponibile';) beforeEach (() => component = mount (); ) it ('test # 1', () => ))

Il primo test è semplice:

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

Qui usiamo il oggetti di scena () metodo che è utile per ottenere gli oggetti di scena di un componente.

Per il secondo test, è possibile interrogare gli elementi in base al loro nome di classe e quindi verificare se il nome del prodotto, la descrizione ecc. Fanno parte di tale elemento innerText

 ('dovrebbe mostrare i dati del prodotto quando gli oggetti di scena sono passati', () => lasciare title = component.find ('. product-title'); expect (title.text ()). toEqual (product.name); description = component.find ('. product-description'); expect (description.text ()). toEqual (product.description);) 

Il testo() il metodo è particolarmente utile in questo caso per recuperare il testo interno di un elemento. Prova a scrivere un'aspettativa per il product.status () e vedere se tutti i test stanno passando.

Per il test finale, stiamo andando a montare il Dettagli del prodotto componente senza puntelli. Quindi cercheremo una classe chiamata '.product-error' e controlleremo se contiene il testo "Scusa, il prodotto non esiste".

 ('dovrebbe mostrare un errore quando gli oggetti di scena non sono passati', () => / * componente senza oggetti di scena * / component = mount (); let node = component.find ('. product-error'); expect (node.text ()). toEqual ('Sorry, Product doesnt exist'); ) 

Questo è tutto. Abbiamo testato con successo il componente in isolamento. Test di questo tipo sono noti come test unitari.

Test di callback tramite stub e spie

Abbiamo appena imparato come testare gli oggetti di scena. Ma per testare veramente un componente in isolamento, è necessario testare anche le funzioni di callback. In questa sezione, scriveremo test per il ProductList componente e creare stub per le funzioni di callback lungo la strada. Ecco le ipotesi che dobbiamo affermare.

  1. Il numero di prodotti elencati dovrebbe essere equivalente al numero di oggetti che il componente riceve come oggetti di scena.
  2. Cliccando su dovrebbe richiamare la funzione di callback.

Creiamo a beforeeach () funzione che riempie i dati di prodotto finti per i nostri test.

src / componenti / __ test __ / ProductList.test.js

 beforeEach (() => productData = [id: 1, nome: 'NIKE Liteforce Blue Sneakers', descrizione: 'Lorem ipsu.', stato: 'Disponibile', // Omissis per brevità])

Ora, montiamo il nostro componente in un altro beforeeach () bloccare.

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

Il ProductList riceve i dati del prodotto tramite oggetti di scena. Oltre a ciò, riceve una richiamata dal genitore. Sebbene sia possibile scrivere test per la funzione di callback del genitore, non è una buona idea se il tuo obiettivo è quello di attenersi ai test unitari. Poiché la funzione di callback appartiene al componente principale, l'integrazione della logica del genitore renderà i test complicati. Invece, creeremo una funzione stub.

Cos'è un mozzicone? 

Uno stub è una funzione fittizia che finge di essere un'altra funzione. Ciò consente di testare in modo indipendente un componente senza importare componenti padre o figlio. Nell'esempio sopra, abbiamo creato una funzione stub chiamata handleProductClick invocando jest.fn ()

Ora abbiamo solo bisogno di trovare il tutto elementi nel DOM e simulare un clic sul primo nodo. Dopo essere stato cliccato, controlleremo se handleProductClick () è stato invocato. Se sì, è giusto dire che la nostra logica funziona come previsto.

('dovrebbe chiamare selectProduct quando cliccato', () => const firstLink = component.find ('a'). first (); firstLink.simulate ('click'); expect (handleProductClick.mock.calls.length) .toEqual (1);))

Enzyme ti consente di simulare facilmente azioni dell'utente come i clic simulare() metodo. handlerProductClick.mock.calls.length restituisce il numero di volte in cui è stata chiamata la funzione simulata. Ci aspettiamo che sia uguale a 1.

L'altro test è relativamente facile. Puoi usare il trova() metodo per recuperare tutto nodi nel DOM. Il numero di i nodi dovrebbero essere uguali alla lunghezza della serie di prodotti Data creata in precedenza. 

 ('dovrebbe mostrare tutti gli articoli del prodotto', () => let links = component.find ('a'); expect (links.length) .toEqual (productData.length);) 

Test dello stato del componente, LifeCycleHook e Method

Il prossimo, stiamo andando a testare il ProductContainer componente. Ha uno stato, un hook del ciclo di vita e un metodo di classe. Ecco le asserzioni che devono essere verificate:

  1. componentDidMount è chiamato esattamente una volta.
  2. Lo stato del componente viene popolato dopo il montaggio del componente.
  3. Il handleProductClick () il metodo dovrebbe aggiornare lo stato quando un ID prodotto viene passato come argomento.

Per verificare se componentDidMount è stato chiamato, lo spiameremo. A differenza di uno stub, una spia viene utilizzata quando è necessario testare una funzione esistente. Una volta impostata la spia, è possibile scrivere asserzioni per confermare se la funzione è stata chiamata.

Puoi spiare una funzione come segue:

src / componenti / __ test __ / ProductContainer.test.js

 ('dovrebbe chiamare componentDidMount once', () => componentDidMountSpy = spyOn (ProductContainer.prototype, 'componentDidMount'); // To be finished);

Il primo parametro a jest.spyOn è un oggetto che definisce il prototipo della classe che stiamo spiando. Il secondo è il nome del metodo che vogliamo spiare. 

Ora esegui il rendering del componente e crea un'asserzione per verificare se è stata chiamata la spia.

 componente = superficiale (); aspetterebbe (componentDidMountSpy) .toHaveBeenCalledTimes (1);

Per verificare che lo stato del componente venga popolato dopo il montaggio del componente, è possibile utilizzare Enzyme stato() metodo per recuperare tutto nello stato. 

('dovrebbe popolare lo stato', () => component = shallow (); aspetta (component.state (). productList.length) .toEqual (4))

Il terzo è un po 'complicato. Dobbiamo verificarlo handleProductClick sta funzionando come previsto. Se vai al codice, vedrai che il handleProductClick () metodo prende un ID prodotto come input e quindi aggiorna this.state.selectedProduct con i dettagli di quel prodotto. 

Per testare questo, abbiamo bisogno di invocare il metodo del componente, e puoi farlo effettivamente chiamando component.instance (). handleProductClick (). Passeremo un ID prodotto di esempio. Nell'esempio seguente, utilizziamo l'id del primo prodotto. Quindi, possiamo verificare se lo stato è stato aggiornato per confermare che l'asserzione è vera. Ecco l'intero codice:

 (dovrebbe avere un metodo di lavoro chiamato handleProductClick ', () => let firstProduct = productData [0] .id; component = shallow (); . Component.instance () handleProductClick (firstProduct); si aspetti (component.state (). selectedProduct) .toEqual (productData [0]); ) 

Abbiamo scritto 10 test e, se tutto va bene, questo è ciò che dovresti vedere:

Sommario

Accidenti! Abbiamo coperto quasi tutto ciò che devi sapere per iniziare a scrivere test in React usando Jest ed Enzyme. Ora potrebbe essere un buon momento per andare al sito web di Enzyme per dare un'occhiata più approfondita alla loro API.

Quali sono i tuoi pensieri su come scrivere test in React? Mi piacerebbe sentirli nei commenti.