Creșteți menținerea codului cu testarea de integrare React
Publicat: 2022-03-11Testele de integrare sunt un punct favorabil între costul și valoarea testelor. Scrierea de teste de integrare pentru o aplicație React cu ajutorul bibliotecii react-testing în loc de sau în plus față de testele unitare ale componentelor poate crește mentenabilitatea codului fără a afecta viteza de dezvoltare.
În cazul în care doriți să obțineți un avans înainte de a continua, puteți vedea un exemplu de utilizare a bibliotecii react-testing-library pentru testele de integrare a aplicației React aici.
De ce să investești în testarea integrării?
„Testele de integrare ating un echilibru excelent în ceea ce privește compromisurile între încredere și viteză/cheltuieli. De aceea este recomandabil să cheltuiți cea mai mare parte (nu tot, țin cont) din efort acolo.”
– Kent C. Dodds în testele scrise. Nu prea multe. Mai ales integrare.
Este o practică obișnuită să scrieți teste unitare pentru componentele React, folosind adesea o bibliotecă populară pentru testarea „enzimelor” React; în special, metoda sa „superficială”. Această abordare ne permite să testăm componentele izolat de restul aplicației. Cu toate acestea, deoarece scrierea aplicațiilor React se referă la compunerea componentelor, testele unitare singure nu asigură că aplicația este fără erori.
De exemplu, modificarea elementelor de recuzită acceptate ale unei componente și actualizarea testelor unitare asociate acesteia poate duce la trecerea tuturor testelor, în timp ce aplicația poate fi încă întreruptă dacă o altă componentă nu a fost actualizată în consecință.
Testele de integrare pot ajuta la menținerea liniștii sufletești în timp ce se efectuează modificări într-o aplicație React, deoarece asigură că compoziția componentelor are ca rezultat UX-ul dorit.
Cerințe pentru testele de integrare a aplicației React
Iată câteva dintre lucrurile pe care dezvoltatorii React doresc să le facă atunci când scriu teste de integrare:
- Testați cazuri de utilizare a aplicației din perspectiva utilizatorului. Utilizatorii accesează informațiile de pe o pagină web și interacționează cu comenzile disponibile.
- Apelurile API simulate pentru a nu depinde de disponibilitatea și starea API-ului pentru trecerea/esuarea testelor.
- API-uri simulate de browser (de exemplu, stocare locală), deoarece pur și simplu nu există în mediul de testare.
- Afirmați starea React DOM (DOM de browser sau un mediu mobil nativ).
Acum, pentru unele lucruri pe care ar trebui să încercăm să le evităm atunci când scriem teste de integrare a aplicației React:
- Detalii de implementare a testului. Modificările de implementare ar trebui să întrerupă un test doar dacă au introdus într-adevăr o eroare.
- Batjocorește prea mult. Vrem să testăm modul în care toate părțile aplicației funcționează împreună.
- Redare superficială. Dorim să testăm compoziția tuturor componentelor din aplicație până la cea mai mică componentă.
De ce să alegeți React-testing-library?
Cerințele menționate mai sus fac din biblioteca de testare reactie o alegere excelentă, deoarece principiul său principal este de a permite testarea componentelor React într-un mod care seamănă cu modul în care sunt utilizate de un om real.
Biblioteca, împreună cu bibliotecile sale opționale însoțitoare, ne permite să scriem teste care interacționează cu DOM și să afirme starea acestuia.
Exemplu de configurare a aplicației
Aplicația pentru care vom scrie exemple de teste de integrare implementează un scenariu simplu:
- Utilizatorul introduce un nume de utilizator GitHub.
- Aplicația afișează o listă de depozite publice asociate cu numele de utilizator introdus.
Modul în care este implementată funcționalitatea de mai sus ar trebui să fie irelevant din perspectiva testării integrării. Cu toate acestea, pentru a fi aproape de aplicațiile din lumea reală, aplicația urmează tipare comune React, de unde aplicația:
- Este o aplicație cu o singură pagină (SPA).
- Emite solicitări API.
- Are management de stat global.
- Sprijină internaționalizarea.
- Utilizează o bibliotecă de componente React.
Codul sursă pentru implementarea aplicației poate fi găsit aici.
Redactarea Testelor de Integrare
Instalarea dependențelor
Cu fire:
yarn add --dev jest @testing-library/react @testing-library/user-event jest-dom nock
Sau cu npm:
npm i -D jest @testing-library/react @testing-library/user-event jest-dom nock
Crearea unui fișier Integration Test Suite
Vom crea un fișier denumit fișier viewGitHubRepositoriesByUsername.spec.js
în folderul ./test
al aplicației noastre. Jest îl va ridica automat.
Importarea dependențelor în fișierul de testare
import React from 'react'; // so that we can use JSX syntax import { render, cleanup, waitForElement } from '@testing-library/react'; // testing helpers import userEvent from '@testing-library/user-event' // testing helpers for imitating user events import 'jest-dom/extend-expect'; // to extend Jest's expect with DOM assertions import nock from 'nock'; // to mock github API import { FAKE_USERNAME_WITH_REPOS, FAKE_USERNAME_WITHOUT_REPOS, FAKE_BAD_USERNAME, REPOS_LIST } from './fixtures/github'; // test data to use in a mock API import './helpers/initTestLocalization'; // to configure i18n for tests import App from '../App'; // the app that we are going to test
Configurarea suită de teste
describe('view GitHub repositories by username', () => { beforeAll(() => { nock('https://api.github.com') .persist() .get(`/users/${FAKE_USERNAME_WITH_REPOS}/repos`) .query(true) .reply(200, REPOS_LIST); }); afterEach(cleanup); describe('when GitHub user has public repositories', () => { it('user can view the list of public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { // arrange // act // assert }); }); });
Note:
- Înainte de toate testele, bate joc de API-ul GitHub pentru a returna o listă de depozite atunci când sunt apelate cu un anumit nume de utilizator.
- După fiecare test, curățați testul React DOM, astfel încât fiecare test să înceapă dintr-un loc curat.
-
describe
blocurile specifică cazul de utilizare a testului de integrare și variațiile de flux. - Variațiile de debit pe care le testăm sunt:
- Utilizatorul introduce un nume de utilizator valid care are asociate depozite publice GitHub.
- Utilizatorul introduce un nume de utilizator valid care nu are depozite publice GitHub asociate.
- Utilizatorul introduce un nume de utilizator care nu există pe GitHub.
- blochează utilizarea apelului invers asincron
it
deoarece cazul de utilizare pe care îl testează are un pas asincron.
Scrierea primului test de flux
În primul rând, aplicația trebuie să fie redată.
const { getByText, getByPlaceholderText, queryByText } = render(<App />);
Metoda de render
importată din modulul @testing-library/react
redă aplicația în testul React DOM și returnează interogări DOM legate de containerul aplicației randate. Aceste interogări sunt folosite pentru a localiza elemente DOM cu care să interacționeze și să se afirme.
Acum, ca prim pas al fluxului testat, utilizatorului i se prezintă un câmp de nume de utilizator și introduce un șir de nume de utilizator în el.
userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS);
userEvent
din modulul @testing-library/user-event
importat are o metodă de type
care imită comportamentul utilizatorului atunci când introduce text într-un câmp de text. Acceptă doi parametri: elementul DOM care acceptă intrarea și șirul pe care îl introduce utilizatorul.

Utilizatorii găsesc de obicei elementele DOM după textul asociat cu acestea. În cazul introducerii, este fie text etichetă, fie text substituent. Metoda de interogare getByPlaceholderText
returnată mai devreme de la render
ne permite să găsim elementul DOM după textul substituent.
Vă rugăm să rețineți că, deoarece textul în sine se poate schimba adesea, cel mai bine este să nu vă bazați pe valorile reale de localizare și să configurați modulul de localizare pentru a returna o cheie a elementului de localizare ca valoare.
De exemplu, când localizarea „en-US” ar returna în mod normal Enter GitHub username
ca valoare pentru cheia userSelection.usernamePlaceholder
, în teste, dorim să returneze userSelection.usernamePlaceholder
.
Când utilizatorul introduce text într-un câmp, ar trebui să vadă valoarea câmpului de text actualizată.
expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS);
În continuare, în flux, utilizatorul face clic pe butonul de trimitere și se așteaptă să vadă lista de depozite.
userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header');
Metoda userEvent.click
imită utilizatorul făcând clic pe un element DOM, în timp ce interogarea getByText
găsește un element DOM după textul pe care îl conține. Modificatorul closest
ne asigură că selectăm elementul de tipul potrivit.
Notă: în testele de integrare, pașii servesc adesea atât rolurilor de act
, cât și de assert
. De exemplu, afirmăm că utilizatorul poate face clic pe un buton făcând clic pe el.
În pasul anterior, am afirmat că utilizatorul vede secțiunea cu lista de depozite a aplicației. Acum, trebuie să afirmăm că, deoarece preluarea listei de depozite din GitHub poate dura ceva timp, utilizatorul vede o indicație că preluarea este în curs. De asemenea, dorim să ne asigurăm că aplicația nu îi spune utilizatorului că nu există depozite asociate cu numele de utilizator introdus în timp ce lista de depozite este încă în curs de preluare.
getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull();
Rețineți că prefixul de interogare getBy
este folosit pentru a afirma că elementul DOM poate fi găsit, iar prefixul de interogare queryBy
este util pentru afirmația opusă. De asemenea, queryBy
nu returnează o eroare dacă nu este găsit niciun element.
Apoi, vrem să ne asigurăm că, în cele din urmă, aplicația termină de preluare a depozitelor și le afișează utilizatorului.
await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, []));
Metoda asincronă waitForElement
este utilizată pentru a aștepta o actualizare DOM care va face adevărată afirmația furnizată ca parametru al metodei. În acest caz, afirmăm că aplicația afișează numele și descrierea pentru fiecare depozit returnat de API-ul GitHub batjocorit.
În cele din urmă, aplicația nu ar trebui să mai afișeze un indicator că depozitele sunt preluate și nu ar trebui să afișeze un mesaj de eroare.
expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull();
Testul nostru de integrare React rezultat arată astfel:
it('user can view the list of public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, [])); expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull(); });
Teste de debit alternativ
Când utilizatorul introduce un nume de utilizator GitHub fără depozite publice asociate, aplicația afișează un mesaj corespunzător.
describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITHOUT_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITHOUT_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.empty')); expect(queryByText('repositories.error')).toBeNull(); }); });
Când utilizatorul introduce un nume de utilizator GitHub care nu există, aplicația afișează un mesaj de eroare.
describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_BAD_USERNAME); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_BAD_USERNAME); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.error')); expect(queryByText('repositories.empty')).toBeNull(); }); });
De ce React Testează Integrarea Rock
Testarea integrării oferă cu adevărat un punct favorabil pentru aplicațiile React. Aceste teste ajută la identificarea erorilor și la utilizarea abordării TDD, în timp ce, în același timp, nu necesită întreținere atunci când implementarea se modifică.
React-testing-library, prezentată în acest articol, este un instrument excelent pentru scrierea testelor de integrare React, deoarece vă permite să interacționați cu aplicația așa cum o face utilizatorul și să validați starea și comportamentul aplicației din perspectiva utilizatorului.
Sperăm că exemplele oferite aici vă vor ajuta să începeți să scrieți teste de integrare pe proiecte React noi și existente. Exemplul de cod complet care include implementarea aplicației poate fi găsit pe GitHub-ul meu.