Unit test in applicazioni native di reazione

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Il test unitario è diventato parte integrante del processo di sviluppo del software. È il livello di test a cui vengono testati i componenti del software. In questo tutorial imparerai come testare le unità di un'applicazione React Native.

React Native è uno dei framework più utilizzati per la creazione di applicazioni mobili. Questo tutorial è rivolto agli sviluppatori che vogliono iniziare a testare le applicazioni React Native che creano. Useremo il framework di test Jest e Enzyme.

In questo articolo, impareremo i principi di base del test, esploreremo varie librerie per testare un'applicazione e vedremo come testare unità (o componenti) di un'applicazione React Native. Lavorando con un'applicazione React Native, consolideremo la nostra conoscenza dei test.

Nota: la conoscenza di base di JavaScript e React Native sarebbe di grande beneficio durante questo tutorial.

Che cos'è il test unitario?

Il test unitario è il livello di test al quale vengono testati i singoli componenti del software. Lo facciamo per garantire che ogni componente funzioni come previsto. Un componente è la più piccola parte testabile del software.

Per illustrare, creiamo un componente Button e simuliamo uno unit test:

 import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;

Questo componente Button ha testo e una funzione onPress . Testiamo questo componente per vedere di cosa tratta lo unit test.

Per prima cosa, creiamo un file di test, chiamato Button.test.js :

 it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });

Qui stiamo testando per vedere se il nostro componente Button esegue il rendering come dovrebbe su tutte le schermate dell'applicazione. Questo è ciò che riguarda lo unit test: testare i componenti di un'applicazione per assicurarsi che funzionino come dovrebbero.

Unit test in applicazioni native di reazione

Un'applicazione React Native può essere testata con una varietà di strumenti, alcuni dei quali sono i seguenti:

  • WebDriver
    Questo strumento di test open source per le app Node.js viene utilizzato anche per testare le applicazioni React Native.
  • Incubo
    Questo automatizza le operazioni di test nel browser. Secondo la documentazione, "l'obiettivo è esporre alcuni semplici metodi che imitano le azioni dell'utente (come goto , type e click ), con un'API che si sente sincrona per ogni blocco di script, piuttosto che callback profondamente nidificati".
  • Scherzo
    Questa è una delle librerie di test più popolari e quella su cui ci concentreremo oggi. Come React, è gestito da Facebook ed è stato creato per fornire una configurazione "zero config" per le massime prestazioni.
  • Moka
    Mocha è una libreria popolare per testare le applicazioni React e React Native. È diventato uno strumento di test preferito dagli sviluppatori a causa della facilità di configurazione e utilizzo e della velocità.
  • Gelsomino
    Secondo la sua documentazione, Jasmine è un framework di sviluppo basato sul comportamento per testare il codice JavaScript.
Altro dopo il salto! Continua a leggere sotto ↓

Introduzione a scherzo ed enzima

Secondo la sua documentazione, "Jest è un delizioso framework di test JavaScript incentrato sulla semplicità". Funziona con configurazione zero. Dopo l'installazione (usando un gestore di pacchetti come npm o Yarn), Jest è pronto per l'uso, senza bisogno di altre installazioni.

Enzyme è un framework di test JavaScript per le applicazioni React Native. (Se stai lavorando con React piuttosto che con React Native, è disponibile una guida.) Utilizzeremo Enzima per testare le unità dell'output della nostra applicazione. Con esso, possiamo simulare il runtime dell'applicazione.

Iniziamo impostando il nostro progetto. Useremo l'app Done With It su GitHub. È un mercato di applicazioni React Native. Inizia clonandolo, accedi alla cartella e installa i pacchetti eseguendo quanto segue per npm...

 npm install

... o questo per Filato:

 yarn install

Questo comando installerà tutti i pacchetti nella nostra applicazione. Una volta fatto, testeremo la coerenza dell'interfaccia utente della nostra applicazione utilizzando gli snapshot, illustrati di seguito.

Istantanee e configurazione scherzosa

In questa sezione testeremo i tocchi degli utenti e l'interfaccia utente dei componenti dell'app testando gli snapshot utilizzando Jest.

Prima di farlo, dobbiamo installare Jest e le sue dipendenze. Per installare Jest per Expo React Native, eseguire il comando seguente:

 yarn add jest-expo --dev

Questo installa jest-expo nella directory della nostra applicazione. Successivamente, dobbiamo aggiornare il nostro file package.json per avere uno script di test:

 "scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }

Aggiungendo questo comando, diciamo a Jest quale pacchetto registrare nella nostra applicazione e dove.

Il prossimo passo è aggiungere altri pacchetti alla nostra applicazione che aiuteranno Jest a fare un test completo. Per npm, esegui questo...

 npm i react-test-renderer --save-dev

… e per Filato, questo:

 yarn add react-test-renderer --dev

Abbiamo ancora una piccola configurazione da fare nel nostro file package.json . Secondo la documentazione di Expo React Native, è necessario aggiungere una configurazione transformIgnorePattern che impedisca l'esecuzione dei test in Jest ogni volta che un file sorgente corrisponde a un test (cioè se viene eseguito un test e un file simile si trova nei node modules del progetto).

 "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }

Ora creiamo un nuovo file, chiamato App.test.js , per scrivere il nostro primo test. Verificheremo se la nostra App ha un elemento figlio nel suo albero:

 import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });

Ora, esegui il yarn test o il suo equivalente npm. Se App.js ha un singolo elemento figlio, il nostro test dovrebbe essere superato, che verrà confermato nell'interfaccia della riga di comando.

Nel codice sopra, abbiamo importato React e react-test-renderer , che esegue il rendering dei nostri test per Expo . Abbiamo convertito l'albero dei componenti <App /> in JSON, quindi abbiamo chiesto a Jest di verificare se il numero restituito di componenti figlio in JSON è uguale a quello previsto.

Altri test istantanei

Come afferma David Adeneye:

"Un test snapshot assicura che l'interfaccia utente (UI) di un'applicazione Web non cambi inaspettatamente. Acquisisce il codice di un componente in un determinato momento, in modo da poter confrontare il componente in uno stato con qualsiasi altro possibile stato che potrebbe assumere.

Questo viene fatto soprattutto quando un progetto coinvolge stili globali che vengono utilizzati in molti componenti. Scriviamo un test di snapshot per App.js per testarne la coerenza dell'interfaccia utente:

 it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); }); it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });

Aggiungi questo ai test che abbiamo già scritto, quindi esegui il yarn test (o il suo equivalente npm). Se il nostro test passa, dovremmo vedere questo:

 PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s

Questo ci dice che i nostri test sono stati superati e il tempo impiegato. Il tuo risultato sarà simile se i test sono stati superati.

Passiamo a prendere in giro alcune funzioni in Jest.

Chiamate API beffarde

Secondo la documentazione di Jest:

Le funzioni fittizie consentono di testare i collegamenti tra il codice cancellando l'effettiva implementazione di una funzione, catturando le chiamate alla funzione (e i parametri passati in quelle chiamate), catturando istanze di funzioni di costruzione quando istanziate con `new` e consentendo test- configurazione temporale dei valori di ritorno.

In poche parole, un mock è una copia di un oggetto o di una funzione senza il vero funzionamento di quella funzione. Imita quella funzione.

I mock ci aiutano a testare le app in tanti modi, ma il vantaggio principale è che riducono il nostro bisogno di dipendenze.

I mock possono essere generalmente eseguiti in due modi. Uno è creare una funzione simulata che viene iniettata nel codice da testare. L'altro è scrivere una funzione fittizia che sovrascrive il pacchetto o la dipendenza collegata al componente.

La maggior parte delle organizzazioni e degli sviluppatori preferisce scrivere simulazioni manuali che imitano la funzionalità e utilizzano dati falsi per testare alcuni componenti.

React Native include il fetch nell'oggetto globale. Per evitare di effettuare chiamate API reali nel nostro unit test, le prendiamo in giro. Di seguito è riportato un modo per deridere tutte, se non la maggior parte, delle nostre chiamate API in React Native e senza la necessità di dipendenze:

 global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };

Qui, abbiamo scritto una funzione che tenta di recuperare un'API una volta. Fatto ciò, restituisce una promessa e, una volta risolta, restituisce il corpo in JSON. È simile alla risposta fittizia per una transazione di recupero non riuscita: restituisce un errore.

Di seguito è riportato il componente product della nostra applicazione, contenente un oggetto product e restituendo le informazioni come props di scena.

 import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;

Immaginiamo di provare a testare tutti i componenti del nostro prodotto. L'accesso diretto al nostro database non è una soluzione fattibile. È qui che entrano in gioco le prese in giro. Nel codice seguente, stiamo cercando di deridere un componente del prodotto utilizzando Jest per descrivere gli oggetti nel componente.

 describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });

Stiamo usando describe di Jest per dettare i test che vogliamo che vengano eseguiti. Nel primo test, stiamo verificando se l'oggetto che stiamo passando è uguale agli oggetti di scena che abbiamo deriso.

Nel secondo test, stiamo passando gli oggetti di scena del customer per assicurarci che sia un prodotto e che corrisponda alle nostre imitazioni. In tal modo, non dobbiamo testare tutti i componenti del nostro prodotto e possiamo anche prevenire i bug nel nostro codice.

Deridere le richieste API esterne

Fino ad ora, abbiamo eseguito test per le chiamate API con altri elementi nella nostra applicazione. Ora prendiamo in giro una chiamata API esterna. Useremo Axios. Per testare una chiamata esterna a un'API, dobbiamo prendere in giro le nostre richieste e anche gestire le risposte che riceviamo. Useremo axios-mock-adapter per prendere in giro Axios. Innanzitutto, dobbiamo installare axios-mock-adapter eseguendo il comando seguente:

 yarn add axios-mock-adapter

La prossima cosa da fare è creare i nostri mock:

 import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });

Qui, chiamiamo ApiClient e gli passiamo un'istanza Axios per deridere le credenziali dell'utente. Stiamo utilizzando un pacchetto chiamato faker.js per generare dati utente falsi, come un indirizzo e-mail e una password.

Il mock si comporta come ci aspettiamo dall'API. Se la richiesta va a buon fine, riceveremo una risposta con un codice di stato 200 per OK. E otterremo un codice di stato di 400 per una richiesta errata al server, che verrebbe inviata con JSON con il messaggio "Nome utente e password errati".

Ora che il nostro mock è pronto, scriviamo un test per una richiesta API esterna. Come prima, useremo le istantanee.

 it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });

In questo caso, stiamo verificando un accesso corretto con le credenziali corrette, utilizzando il JavaScript nativo async await di contenere i nostri input. Nel frattempo, la funzione authenticateUser di Jest autentica la richiesta e si assicura che corrisponda ai nostri snapshot precedenti. Successivamente, verifichiamo un accesso non riuscito in caso di credenziali errate, come indirizzo e-mail o password, e inviamo un errore come risposta.

Ora, esegui il test del yarn test o npm test . Sono sicuro che tutti i tuoi test passeranno.

Vediamo come testare i componenti in una libreria di gestione dello stato, Redux.

Testare le azioni Redux e i riduttori utilizzando le istantanee

Non si può negare che Redux sia uno dei gestori di stato più utilizzati per le applicazioni React. La maggior parte delle funzionalità in Redux implica un dispatch , che è una funzione dell'archivio Redux utilizzata per attivare un cambiamento nello stato di un'applicazione. Testare Redux può essere complicato perché le actions di Redux crescono rapidamente in termini di dimensioni e complessità. Con le istantanee Jest, questo diventa più facile. La maggior parte dei test con Redux si riduce a due cose:

  • Per testare actions , creiamo redux-mock-store e inviamo le azioni.
  • Per testare i riduttori, importiamo il reducer e gli passiamo un oggetto stato e azione.

Di seguito è riportato un test Redux con snapshot. Testeremo le azioni inviate autenticando l'utente al SIGN-IN e vedendo come l'azione LOGOUT viene gestita dal riduttore user .

 import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });

Nel primo test, descriviamo l'autenticazione di accesso e creiamo un negozio fittizio. Lo facciamo importando prima un mockStore da Redux, quindi importando un metodo chiamato testUser da Jest per aiutarci a prendere in giro un utente. Successivamente, verifichiamo quando l'utente accede correttamente all'applicazione utilizzando un indirizzo e-mail e una password che corrispondono a quelli nel nostro archivio di istantanee. Pertanto, lo snapshot garantisce che gli oggetti immessi dall'utente corrispondano ogni volta che viene eseguito un test.

Nel secondo test, stiamo testando quando l'utente si disconnette. Una volta che il nostro snapshot del riduttore conferma che un utente si è disconnesso, torna allo stato iniziale dell'applicazione.

Successivamente, testiamo eseguendo il yarn test . Se i test sono stati superati, dovremmo vedere il seguente risultato:

 PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s

Conclusione

Con Jest, testare le applicazioni React Native non è mai stato così facile, specialmente con gli snapshot, che assicurano che l'interfaccia utente rimanga coerente indipendentemente dagli stili globali. Inoltre, Jest ci consente di deridere alcune chiamate e moduli API nella nostra applicazione. Possiamo andare oltre testando i componenti di un'applicazione React Native.

Ulteriori risorse

  • "Una guida pratica per testare le applicazioni native di reazione con Jest", David Adeneye, Smashing Magazine
  • Documentazione scherzosa
  • “Testing With Jest”, documentazione Expo React Native
  • "Imparare a testare la reazione nativa con Jest", Jason Gaare