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.
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:
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.
Ecco i test che abbiamo scritto nell'ultimo tutorial:
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.
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.
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 ()))
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 è evidenziataIl test unitario cercherà di affermare le seguenti ipotesi:
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.
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.
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.
dovrebbe richiamare la funzione di callback.Creiamo a beforeeach ()
funzione che riempie i dati di prodotto finti per i nostri test.
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.
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);)
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:
componentDidMount
è chiamato esattamente una volta.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:
('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:
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.