Testarea unitară în aplicațiile React Native

Publicat: 2022-03-10
Rezumat rapid ↬ Testarea unitară a devenit o parte integrantă a procesului de dezvoltare software. Este nivelul de testare la care sunt testate componentele software-ului. În acest tutorial, veți învăța cum să testați unitățile unei aplicații React Native.

React Native este unul dintre cele mai utilizate framework-uri pentru construirea de aplicații mobile. Acest tutorial este destinat dezvoltatorilor care doresc să înceapă să testeze aplicațiile React Native pe care le construiesc. Vom folosi cadrul de testare Jest și Enzyme.

În acest articol, vom afla principiile de bază ale testării, vom explora diferite biblioteci pentru testarea unei aplicații și vom vedea cum să testăm unitățile (sau componentele) unei aplicații React Native. Lucrând cu o aplicație React Native, ne vom consolida cunoștințele despre testare.

Notă: cunoștințele de bază despre JavaScript și React Native ar fi de mare beneficiu pe măsură ce lucrați prin acest tutorial.

Ce este testarea unitară?

Testarea unitară este nivelul de testare la care sunt testate componentele individuale ale software-ului. O facem pentru a ne asigura că fiecare componentă funcționează conform așteptărilor. O componentă este cea mai mică parte testabilă a software-ului.

Pentru a ilustra, să creăm o componentă Button și să simulăm un test unitar:

 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;

Această componentă Button are text și o funcție onPress . Să testăm această componentă pentru a vedea despre ce este vorba despre testarea unitară.

Mai întâi, să creăm un fișier de testare, numit Button.test.js :

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

Aici, testăm pentru a vedea dacă componenta noastră Button se redă așa cum ar trebui pe toate ecranele aplicației. Acesta este ceea ce înseamnă testarea unitară: testarea componentelor unei aplicații pentru a vă asigura că funcționează așa cum ar trebui.

Testarea unitară în aplicațiile React Native

O aplicație React Native poate fi testată cu o varietate de instrumente, dintre care unele sunt următoarele:

  • WebDriver
    Acest instrument de testare open-source pentru aplicațiile Node.js este folosit și pentru a testa aplicațiile React Native.
  • Coșmar
    Acest lucru automatizează operațiunile de testare în browser. Potrivit documentației, „scopul este de a expune câteva metode simple care imită acțiunile utilizatorului (cum ar fi goto , type and click ), cu un API care se simte sincron pentru fiecare bloc de scripting, mai degrabă decât apeluri imbricate profund.”
  • Glumă
    Aceasta este una dintre cele mai populare biblioteci de testare și cea pe care ne vom concentra astăzi. La fel ca React, este întreținut de Facebook și a fost creat pentru a oferi o configurație „zero config” pentru performanță maximă.
  • Moca
    Mocha este o bibliotecă populară pentru testarea aplicațiilor React și React Native. A devenit un instrument de testare ales pentru dezvoltatori, datorită cât de ușor este de configurat și utilizat și cât de rapid este.
  • Iasomie
    Conform documentației sale, Jasmine este un cadru de dezvoltare bazat pe comportament pentru testarea codului JavaScript.
Mai multe după săritură! Continuați să citiți mai jos ↓

Introducere în glumă și enzimă

Conform documentației sale, „Jest este un cadru de testare JavaScript încântător, cu accent pe simplitate”. Funcționează cu configurație zero. La instalare (folosind un manager de pachete, cum ar fi npm sau Yarn), Jest este gata de utilizare, fără alte instalări necesare.

Enzyme este un cadru de testare JavaScript pentru aplicațiile React Native. (Dacă lucrați cu React mai degrabă decât cu React Native, este disponibil un ghid.) Vom folosi Enzyme pentru a testa unitățile de ieșire ale aplicației noastre. Cu el, putem simula timpul de rulare al aplicației.

Să începem prin a configura proiectul nostru. Vom folosi aplicația Done With It pe GitHub. Este o piață de aplicații React Native. Începeți prin a-l clona, ​​navigați în folder și instalați pachetele rulând următoarele pentru npm...

 npm install

… sau asta pentru Yarn:

 yarn install

Această comandă va instala toate pachetele din aplicația noastră. Odată ce s-a terminat, vom testa consistența interfeței de utilizare a aplicației noastre folosind instantanee, prezentate mai jos.

Instantanee și configurație de glumă

În această secțiune, vom testa atingerea utilizatorului și interfața de utilizare a componentelor aplicației, testând instantanee folosind Jest.

Înainte de a face asta, trebuie să instalăm Jest și dependențele sale. Pentru a instala Jest pentru Expo React Native, executați următoarea comandă:

 yarn add jest-expo --dev

Aceasta instalează jest-expo în directorul aplicației noastre. Apoi, trebuie să actualizăm fișierul package.json pentru a avea un script de testare:

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

Adăugând această comandă, îi spunem lui Jest ce pachet să înregistreze în aplicația noastră și unde.

Urmează adăugarea altor pachete la aplicația noastră care îl vor ajuta pe Jest să facă un test cuprinzător. Pentru npm, rulați acest...

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

... și pentru Yarn, aceasta:

 yarn add react-test-renderer --dev

Mai avem o mică configurație de făcut în fișierul nostru package.json . Conform documentației Expo React Native, trebuie să adăugăm o configurație transformIgnorePattern care împiedică rularea testelor în Jest ori de câte ori un fișier sursă se potrivește cu un test (adică dacă se face un test și se găsește un fișier similar în node modules ale proiectului).

 "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/.*)" ] }

Acum, să creăm un fișier nou, numit App.test.js , pentru a scrie primul nostru test. Vom testa dacă App noastră are un element copil în arborele său:

 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); }); });

Acum, rulați yarn test sau echivalentul său npm. Dacă App.js are un singur element copil, testul nostru ar trebui să treacă, ceea ce va fi confirmat în interfața de linie de comandă.

În codul de mai sus, am importat React și react-test-renderer , care redă testele noastre pentru Expo . Am convertit arborele de componente <App /> în JSON și apoi l-am rugat pe Jest să vadă dacă numărul returnat de componente secundare în JSON este egal cu ceea ce ne așteptăm.

Mai multe teste instantanee

După cum afirmă David Adeneye:

„Un test de instantaneu se asigură că interfața cu utilizatorul (UI) a unei aplicații web nu se schimbă în mod neașteptat. Captează codul unei componente la un moment dat, astfel încât să putem compara componenta într-o stare cu orice altă stare posibilă pe care ar putea-o avea.”

Acest lucru se realizează mai ales atunci când un proiect implică stiluri globale care sunt utilizate pe o mulțime de componente. Să scriem un test instantaneu pentru App.js pentru a-și testa consistența UI:

 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(); });

Adăugați asta la testele pe care le-am scris deja și apoi rulați yarn test (sau echivalentul său npm). Dacă testul nostru trece, ar trebui să vedem asta:

 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

Acest lucru ne spune că testele noastre au trecut și timpul pe care l-au luat. Rezultatul dvs. va arăta similar dacă testele au trecut.

Să trecem la batjocură de unele funcții din Jest.

Apeluri API batjocoritoare

Conform documentației lui Jest:

Funcțiile simulate vă permit să testați legăturile dintre cod ștergând implementarea efectivă a unei funcții, captând apeluri către funcție (și parametrii trecuți în acele apeluri), captând instanțe ale funcțiilor de constructor atunci când sunt instanțiate cu `new` și permițând testarea- configurarea timpului a valorilor returnate.

Mai simplu spus, o simulare este o copie a unui obiect sau a unei funcții fără funcționarea reală a acelei funcții. Imită această funcție.

Mock-urile ne ajută să testăm aplicațiile în atât de multe moduri, dar principalul avantaj este că ne reduc nevoia de dependențe.

Mock-urile pot fi de obicei efectuate în unul din două moduri. Una este de a crea o funcție simulată care este injectată în codul de testat. Celălalt este să scrieți o funcție simulată care suprascrie pachetul sau dependența care este atașată la componentă.

Majoritatea organizațiilor și dezvoltatorilor preferă să scrie false manuale care imită funcționalitatea și folosesc date false pentru a testa unele componente.

React Native include fetch în obiectul global. Pentru a evita efectuarea de apeluri API reale în testul unitar, le batem joc. Mai jos este o modalitate de a bate joc de toate, dacă nu de majoritatea apelurilor noastre API în React Native și fără a fi nevoie de dependențe:

 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) ); };

Aici, am scris o funcție care încearcă să preia un API o dată. După ce a făcut acest lucru, returnează o promisiune și, când este rezolvată, returnează corpul în JSON. Este similar cu răspunsul simulat pentru o tranzacție de preluare eșuată - returnează o eroare.

Mai jos este componenta de product a aplicației noastre, care conține un obiect product și returnează informațiile ca elemente de props .

 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;

Să ne imaginăm că încercăm să testăm toate componentele produsului nostru. Accesarea directă a bazei noastre de date nu este o soluție fezabilă. Aici intră în joc batjocură. În codul de mai jos, încercăm să batem joc de o componentă a produsului folosind Jest pentru a descrie obiectele din componentă.

 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); }); });

Folosim describe de la Jest pentru a dicta testele pe care vrem să le facem. În primul test, verificăm dacă obiectul pe care îl trecem este egal cu elementele de recuzită pe care le-am batjocorit.

În al doilea test, trecem recuzita customer pentru a ne asigura că este un produs și că se potrivește cu simulacrele noastre. Făcând acest lucru, nu trebuie să testăm toate componentele produsului nostru și, de asemenea, putem preveni erorile din codul nostru.

Batjocorirea solicitărilor API externe

Până acum, am efectuat teste pentru apeluri API cu alte elemente din aplicația noastră. Acum să ne batem joc de un apel API extern. Vom folosi Axios. Pentru a testa un apel extern către un API, trebuie să ne batem joc de solicitările și, de asemenea, să gestionăm răspunsurile pe care le primim. Vom folosi axios-mock-adapter pentru a bate joc de Axios. Mai întâi, trebuie să instalăm axios-mock-adapter rulând comanda de mai jos:

 yarn add axios-mock-adapter

Următorul lucru de făcut este să ne creați simulacrele:

 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']; });

Aici, apelăm ApiClient și îi transmitem o instanță Axios pentru a bate joc de acreditările utilizatorului. Utilizăm un pachet numit faker.js pentru a genera date false ale utilizatorului, cum ar fi o adresă de e-mail și o parolă.

Simularea se comportă așa cum ne așteptăm ca API-ul. Dacă solicitarea are succes, vom primi un răspuns cu un cod de stare de 200 pentru OK. Și vom primi un cod de stare de 400 pentru o solicitare greșită către server, care va fi trimisă cu JSON cu mesajul „Nume de utilizator și parolă incorecte”.

Acum că simularea noastră este gata, să scriem un test pentru o solicitare API externă. Ca și înainte, vom folosi instantanee.

 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(); });

Aici, testăm pentru o conectare reușită cu acreditările corecte, utilizând JavaScript nativ async await pentru a păstra intrările noastre. Între timp, funcția authenticateUser de la Jest autentifică cererea și se asigură că se potrivește cu instantaneele noastre anterioare. Apoi, testăm pentru o conectare nereușită în cazul unor acreditări greșite, cum ar fi adresa de e-mail sau parola, și trimitem o eroare ca răspuns.

Acum, rulați yarn test sau npm test . Sunt sigur că toate testele tale vor trece.

Să vedem cum să testăm componente într-o bibliotecă de management de stat, Redux.

Testarea acțiunilor și reductoarelor Redux folosind instantanee

Nu se poate nega faptul că Redux este unul dintre cei mai folosiți manageri de stat pentru aplicațiile React. Cea mai mare parte a funcționalității din Redux implică o dispatch , care este o funcție a magazinului Redux care este utilizată pentru a declanșa o schimbare a stării unei aplicații. Testarea Redux poate fi dificilă, deoarece actions Redux cresc rapid în dimensiune și complexitate. Cu instantaneele Jest, acest lucru devine mai ușor. Cele mai multe teste cu Redux se rezumă la două lucruri:

  • Pentru a testa actions , creăm redux-mock-store și trimitem acțiunile.
  • Pentru a testa reductoarele, importăm reducer și îi transmitem un obiect de stare și acțiune.

Mai jos este un test Redux cu instantanee. Vom testa acțiunile trimise prin autentificarea utilizatorului la SIGN-IN și să vedem cum este gestionată acțiunea LOGOUT de către reductorul de 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(); }); });

În primul test, descriem autentificarea de conectare și creăm un magazin simulat. Facem acest lucru importând mai întâi un mockStore din Redux și apoi importând o metodă numită testUser din Jest pentru a ne ajuta să batem joc de un utilizator. Apoi, testăm când utilizatorul se conectează cu succes la aplicație folosind o adresă de e-mail și o parolă care se potrivesc cu cele din magazinul nostru de instantanee. Deci, instantaneul asigură că obiectele pe care utilizatorul le introduce se potrivesc de fiecare dată când se rulează un test.

În al doilea test, testăm când utilizatorul se deconectează. Odată ce instantaneul nostru reductor confirmă că un utilizator s-a deconectat, acesta revine la starea inițială a aplicației.

În continuare, testăm rulând yarn test . Dacă testele au trecut, ar trebui să vedem următorul rezultat:

 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

Concluzie

Cu Jest, testarea aplicațiilor React Native nu a fost niciodată mai ușoară, mai ales cu instantanee, care asigură că interfața de utilizare rămâne consistentă indiferent de stilurile globale. De asemenea, Jest ne permite să batem joc de anumite apeluri și module API din aplicația noastră. Putem duce acest lucru mai departe testând componente ale unei aplicații React Native.

Resurse suplimentare

  • „Un ghid practic pentru testarea aplicațiilor native React cu Jest”, David Adeneye, Smashing Magazine
  • Gest documentație
  • „Testing With Jest”, documentație Expo React Native
  • „Învățam să testăm React Native With Jest”, Jason Gaare