Definitiv guide till enhetstestning i React-applikationer med Jest och React-Testing

Beskrivning

Som utvecklare borde en av sakerna överst på din lista vara att skicka felfri kod. Ingenting kan vara värre än att på torsdagskvällen få reda på att ändringarna du gjorde i måndags bröt liveapplikationen. Det enda sättet att säkerställa att din app fungerar enligt både system- och användarkrav är att testa det!

Testning är en avgörande komponent i varje livscykel för mjukvaruutveckling och säkerställer att en mjukvara fungerar korrekt och enligt plan. Webbutveckling, utveckling av mobilappar och, ännu viktigare i vårt sammanhang, React-applikationer följer alla samma principer.

Reactkomponenter kan testas på några olika sätt, i stort sett indelade i två grupper:

  • Återge komponentträd i en enkel testmiljö och göra påståenden om deras prestanda
  • Springa "end-to-end-tester", vilket innebär att man testar en hel applikation i en realistisk webbläsarmiljö

Även om testning av React-appar kan göras på ett antal sätt, kommer vi i den här guiden att skapa en React-app och täcka en komplett guide till hur vi kan utföra enhetstester på React-applikationen med det finns och React Testing Library så att du kan finslipa dina testfärdigheter och lära dig hur du skapar en tam React-applikation.

Notera: Du kan få tillgång till förvaret för den här guiden och leka med allt som finns där med hjälp av detta länk på GitHub.

Vad är testning?

Först av allt, låt oss sätta saker i ett perspektiv. Testning är ett mycket brett begrepp och kan syfta på manuell testning, enhetstestning, regressionstestning, integrationstestning, lasttestning, etc.

Inom ramen för enhetstestning som vi kommer att fokusera på idag – vi testar funktion av distinkta enheter, vanligtvis på metodnivå. Detta kan testa de numeriska värdena för utgångarna, längden på utgångsvärdena, deras former, hur metoden reagerar på ogiltig inmatning, etc.

Eftersom de flesta goda programvarupraxis förespråkar korta, handlingsbara metoder/funktioner som är fristående med ett tydligt syfte, kommer många metoder att kalla andra metoder. Vanligtvis vill du testa både de interna metoderna och de externa metoderna, för att säkerställa att eventuella ändringar du gör när du omstrukturerar, fixar buggar eller förbättrar en funktion inte bryter någon annan funktionalitet.

In Testdriven utveckling (TDD), uppmuntras du att skriva ett test och ett förväntat värde innan du skriver logiken för en metod. Naturligtvis kommer det att misslyckas först. Efter det får du det dock bara att fungera, och när det klarar testet börjar du omfaktorisera det för att göra det kortare, renare, snabbare, etc. Så länge utgången förblir densamma vet du att du inte har brutit någonting under refaktorisering!

Att skriva dina egna enhetstester sätter dig i någons tankesätt med hjälp av dina metoder, snarare än någon skrivning dessa metoder, som ofta hjälper till att ta en ny titt på en funktion, innehåller ytterligare kontroller och validering och letar efter buggar. Ibland leder det till designändringar för att göra koden mer testbara, såsom frikopplingsfunktioner för att möjliggöra numerisk testning för varje enskild komponent.

När en baslinje är etablerad och din kod klarar testerna kan du göra ändringar och validera att de enskilda enheterna (vanligtvis metoder) fungerar individuellt. Testning är särskilt användbart när det finns uppdateringar av en kodbas.

Metoder för att testa

Testning kan göras på två olika sätt: manuellt och automatiskt. Genom att interagera direkt med en applikation verifierar manuell testning att den fungerar korrekt. Automatiserad testning är metoden att skriva program för att utföra kontrollerna åt dig.

Manuell testning

De flesta utvecklare granskar sin kod manuellt, eftersom detta är det snabbaste, mest naturliga och enklaste sättet att snabbt testa en funktionalitet.

Manuell testning är nästa logiska steg som följer efter skrivfunktionalitet, ungefär som att smaka på en maträtt kommer efter att ha kryddat den (lägger till en funktion) för att kontrollera om den fungerade som avsett.

Anta att du som anställd utvecklare bygger ett registreringsformulär. Du stänger inte bara din textredigerare och informerar din chef att formuläret är komplett efter kodning. Du öppnar webbläsaren, går igenom registreringsformuläret och ser till att allt går som planerat. Med andra ord, du kommer att testa koden manuellt.

Manuell testning är idealisk för små projekt, och du behöver inga automatiserade tester om du har en att-göra-lista-applikation som du kan kontrollera manuellt varannan minut. Dock beroende på manuell testning blir svår när din app växer – det kan bli för lätt att tappa koncentrationen och glömma att kolla något, kanske. Med en växande lista på samverkande komponenter, manuell testning blir ännu svårare, speciellt om du har testat något, och gått vidare till ett nytt objekt och brutit den sista funktionen, så du inte testar det igen på ett tag utan att veta att det nu är trasigt.

Enkelt uttryckt är manuell testning okej för att börja – men skalas inte bra och garanterar inte kodkvalitet för större projekt. Den goda nyheten är att datorer är fantastiska vid uppgifter som dessa, vi har automatiserade tester att tacka!

Automatiserad testning

I automatiserad testning skriver du ytterligare kod för att testa din applikationskod. När du har skrivit testkoden kan du testa din app så många gånger du vill med minimal ansträngning.

Det finns många tekniker för att skriva automatiserade test:

  • Att skriva program för att automatisera en webbläsare,
  • Ringa funktioner direkt från din källkod,
  • Jämför skärmdumpar av din renderade applikation...

Varje teknik har sin egen uppsättning fördelar, men de har alla en sak gemensamt – de sparar tid och säkerställer högre kodkvalitet jämfört med manuell testning!

Automatiserade tester är utmärkta för att säkerställa att din applikation fungerar som planerat. De gör det också lättare att gå över kodändringar i en applikation.

Typer av testning

Hittills har vi tittat på tester på hög nivå. Det är på tiden att diskutera de olika typer av prov som kan skrivas.

Det finns tre typer av front-end-applikationstester:

  • Enhetstester: I enhetstester testas enskilda enheter eller komponenter i programvaran. En individuell enhet är en enda funktion, metod, procedur, modul, komponent eller objekt. Ett enhetstest isolerar och verifierar en kodsektion för att verifiera att varje enhet av programvarans kod fungerar som förväntat.
    Enskilda moduler eller funktioner testas i enhetstestning för att säkerställa att de fungerar som de ska, och alla komponenter testas också individuellt. Enhetstestning skulle till exempel innefatta att avgöra om en funktion, en sats eller en loop i ett program fungerar korrekt.

  • Snapshot tester: Den här typen av test säkerställer att en webbapplikations användargränssnitt (UI) inte ändras oväntat. Den fångar koden för en komponent vid en specifik tidpunkt, vilket gör att vi kan jämföra komponenten i ett tillstånd med vilket annat möjligt tillstånd som det kan ta.
    Ett typiskt testscenario för ögonblicksbild innefattar att rendera en UI-komponent, ta en ögonblicksbild och jämföra ögonblicksbilden med en ögonblicksbildsreferensfil som bevaras med testet. Om de två ögonblicksbilderna skiljer sig, kommer testet att misslyckas eftersom ändringen antingen var oväntad eller att referensögonblicksbilden behövde uppdateras för att återspegla den nya UI-komponenten.

  • Slut-till-ände tester: End-to-end-test är den enklaste typen av test att förstå. End-to-end-tester i front-end-applikationer automatiserar en webbläsare för att säkerställa att en applikation fungerar korrekt ur användarens perspektiv.
    End-to-end-tester sparar mycket tid. Du kan köra ett end-to-end-test så många gånger du vill efter att du har skrivit det. Tänk på hur mycket tid en serie med hundratals av dessa test potentiellt skulle kunna spara jämfört med att skriva test för varje enskild enhet.
    Med alla fördelar som de ger, har end-to-end-tester några problem. Till att börja med är tester från början till slut tidskrävande. Ett annat problem med end-to-end-tester är att de kan vara svåra att felsöka.

Notera: För att undvika reproducerbarhetsproblem kan end-to-end-tester köras i en reproducerbar miljö, till exempel en Docker-behållare. Docker-containrar och end-to-end-tester ligger utanför ramen för den här guiden, men du bör undersöka dem om du vill köra end-to-end-tester för att undvika problemet med fel på olika maskiner.

Om du vill förstå grunderna i end-to-end-testning med Cypress – läs vår "End-to-end-testning i JavaScript med Cypress"!

Fördelar och nackdelar med testning

Även om testning är viktigt och borde göras, som vanligt, har det både fördelar och nackdelar.

Fördelar

  • Det skyddar mot oväntad regression
  • Att testa korrekt ökar kodens kvalitet avsevärt
  • Det tillåter utvecklaren att koncentrera sig på den aktuella uppgiften snarare än det förflutna
  • Det möjliggör modulär konstruktion av annars svårbyggda applikationer
  • Det eliminerar behovet av manuell verifiering

Nackdelar

  • Du måste skriva mer kod utöver att felsöka och underhålla den, och många tycker att det är onödigt omkostnader i mindre projekt, oavsett fördelarna
  • Icke-kritiska/godartade testfel kan resultera i att appen avvisas under kontinuerlig integration

Översikt över enhetstestning

Hittills har vi tittat på tester i allmänhet. Nu är det dags att dyka ner i allt som rör enhetstestning och hur man skriver enhetstester i React-applikationer!

Innan vi definierar enhetstestning är det nödvändigt för oss att komma till vetskap om att en bra testmetod syftar till att påskynda utvecklingstiden, minska buggar i en applikation och förbättra kodens kvalitet, medan en dålig testmetod skulle lamslå en applikation. Som ett resultat av detta borde vi som mjukvaruutvecklare lära oss effektiva metoder för enhetstestning, och en av dem är enhetstestning.

En enkel definition av testning är att det är processen att kontrollera att en applikation fungerar korrekt. Enhetstestning är processen att köra tester mot komponenterna eller funktionerna i en applikation. Enhetstest är funktioner som anropar isolerade versioner av funktionerna i din källkod för att verifiera att de beter sig som de ska, deterministiskt.

Fördelar med enhetstester

Enhetstester är snabba och kan köras på några sekunder (antingen individuellt för en ny funktion eller globalt kör alla tester), vilket ger utvecklare omedelbar feedback om huruvida en funktion är trasig eller inte. De hjälper också till att tillhandahålla dokumentation, för om en ny utvecklare går med i ett projekt, skulle de behöva veta hur olika enheter i kodbasen beter sig; detta kan man veta genom att titta på resultaten av enhetstester.

Nackdelar med enhetstester

Även om enhetstester har sina goda sidor, har de också sina egna problem. Ett problem är att refaktoriseringskoden när det kommer till designförändringar, eftersom dessa tenderar att vara svårare med enhetstester. Säg till exempel att du har en komplicerad funktion med dess enhetstester och vill dela upp den funktionen i flera modulära funktioner. Ett enhetstest kommer förmodligen att misslyckas för den funktionen, och du måste fasa ut det och skriva två enhetstester för de delade funktionerna. Det är därför enhetstestning implicit uppmuntrar att dela upp dem i förväg och testa dem individuellt, vilket leder till mer testbara, modulära kodkomponenter. I vissa fall kan du dock inte förutse de möjliga förändringarna längre fram, och den tid det skulle ta att uppdatera enhetstesterna gör seriösa refaktoreringsprocesser mindre tilltalande.

Ett annat problem med enhetstestning är att den bara kontrollerar enskilda delar av en app, även om den delen är en logisk kombination av flera mindre delar – det finns ingen enhetstest för hela ansökan. Enskilda delar av en app kan fungera korrekt, men om hur de beter sig när de kombineras inte testas, kan testerna göras oanvändbara. Det är därför enhetstester bör kompletteras med end-to-end-tester eller integrationstester eller helst – båda.

Enhet som testar en React-applikation – Demoprojekt

Låt oss ta en titt på ett verkligt exempel på enhetstestning av en React-applikation!

I den här demon kommer vi att testa en Counter-app med många olika delar till den. Trots att den låter som en ganska enkel app, skulle den fungera som ett bra exempel för att lära sig hur enhetstestning fungerar. Kärnan i att testa den här appen är att det finns olika aspekter av komponenten som beror på hur användaren interagerar med den.

Projektinställning

Smakämnen create-react-app kommandot, byggt av React-teamet, är det bästa sättet att komma igång med att skapa en verklig och storskalig React-applikation eftersom den är redo att användas och fungerar enkelt med Skämt testa bibliotek. Om du öppnar package.json fil kommer du att upptäcka att vi har standardstöd för det finns och Reager testa bibliotek i setupTests.js fil. Detta eliminerar behovet för oss att manuellt installera Jest i vårt projekt om vi behöver!

Om du inte redan har använt den – kör den med npx, som kommer att installera den för senare användning:

$ npx create-react-app react-unit-tests

Om du redan har verktyget installerat, skapa en React-app och namnge den react-unit-tests:

$ create-react-app react-unit-tests

Notera: npx använder den senaste versionen av create-react-app, medan en globalt installerad version kanske inte. Det rekommenderas i allmänhet att köra verktyget igenom npx för att säkerställa de senaste versionerna, såvida du inte medvetet vill använda en annan version.

Därefter går vi in ​​i projektkatalogen och startar utvecklingsservern:

$ cd react-unit-tests && npm start
// OR
$ cd react-unit-tests && yarn start

Detta kommer att mata ut vår nyskapade app i webbläsaren på localhost:3000.

Notera: En praktisk funktion här är det varm omladdning stöds som standard, så det finns ingen anledning att fortsätta ladda om webbläsaren bara för att se nya ändringar, eller manuellt installera nodemon eller liknande bibliotek.

Bygga räknekomponenten

I src katalogen för vårt projekt, skapa en ny fil som heter Counter.js. I Counter.js, kommer vi att definiera alla delar av komponenten. Den kommer att innehålla olika funktioner i räknaren, inklusive increment(), decrement(), restart()och switchSign(), som inverterar räknevärdet från negativt till positivt när du klickar på det. Dessa funktioner skapas för att manipulera det initiala räknevärdet (som skickas in som en rekvisita):


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount((prev) => prev + 1);
  };

  const decrement = () => {
    setCount((prev) => prev - 1);
  };

  const restart = () => {
    setCount(0);
  };

  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
        Count: <h3>{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

Uppdatera sedan App.js:


import "./App.css";
import Counter from "./Counter";

function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

export default App;

Nu kan vi se räknarappen i webbläsaren:

Definitiv guide till enhetstestning i React-applikationer med Jest och React-Testing PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Skapa tester för komponenter

Låt oss skapa en testfil som heter Counter.test.js för att representera testet för Counter-komponenten. Se till att också radera App.test.js så att det inte skapar oönskade resultat medan vi kör tester.

Notera: En vanlig praxis är att namnge dina testfiler med suffixet på .test.js, speglar namnet på filen/komponenten du testar. Detta säkerställer en kontinuitet mellan testfilerna, ändringar görs endast i de filer som är relevanta för koden du uppdaterar när du trycker på ändringar (lägre antal sammanslagningskonflikter) och är läsbar.

Dessutom finns testfiler vanligtvis i en /test katalog parallell till din källkods rotkatalog, men detta är också teamberoende.

In Counter.test.js, importerar vi först Counter komponent, starta sedan testet med describe() funktion för att beskriva alla olika funktioner som kan hända inom komponenten.

Smakämnen describe() funktion används för att gruppera specifika uppsättningar av tester som kan förekomma på en komponent med olika it() och test() metoder. Det är ett slags logiskt omslag, där du, ja, beskriver vad en serie tester gör, med varje it() vara ett funktionstest för en enhet.

Att testa dina React-komponenter kan göras på ett sätt så att vi kan använda en testrenderare för att snabbt skapa ett serialiserbart värde för ditt React-träd istället för att generera det grafiska användargränssnittet, vilket skulle innebära att skapa hela appen.

Testa det initiala räknevärdet

När man testar hjälper det att skapa en systematisk lista över funktioner och aspekter av en given funktion – de tillstånd som komponenter kan vara i, vad som kan tänkas påverka dem, etc.

Det första vi ska testa är initialt räknevärde och hur komponenten hanterar rekvisitan som ställer in den. Med it() metod, kontrollerar vi om räknarappen faktiskt visar det exakta initiala räknevärdet som har skickats som en rekvisita, vilket är 0 i det här fallet och skicka en återuppringningsfunktion som beskriver alla åtgärder som kommer att inträffa i testet:


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByTestId("count").textContent).toEqual(0);
  });
});

Här använde vi screen instans från React Testing-biblioteket för att rendera komponenten för teständamål. Det är användbart att göra en låtsasversion av en komponent som ska testas. Och sedan

element som håller count värdet kommer att förändras dynamiskt, vi använder screen.getByTestId() funktion för att lyssna på den och hämta dess värde med textContent fast egendom.

Kolla in vår praktiska, praktiska guide för att lära dig Git, med bästa praxis, branschaccepterade standarder och medföljande fuskblad. Sluta googla Git-kommandon och faktiskt lära Det!

Notera: Smakämnen screen funktion returnerar en matchande DOM-nod för valfri fråga eller ger ett fel om inget element hittas.

Då, i Counter.js komponent kommer vi att lyssna på

element under testning genom att ställa in a data-testid attribut till elementet med ett värde count:


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  const increment = () => {
    setCount((prev) => prev + 1);
  };
  const decrement = () => {
    setCount((prev) => prev - 1);
  };
  const restart = () => {
    setCount(0);
  };
  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
      	
        Count: <h3 data-testid="count">{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

För att testa om initialen count värdet är lika med 0, vi använder expect() metod för att beskriva vad som förväntas av testet vi har satt! I vårt fall förväntar vi oss att det initiala räknevärdet är 0 så vi använder toEqual() metod, som används för att avgöra om värdena för två objekt matchar. Istället för att bestämma objektets identitet toEqual() matcher kontrollerar rekursivt alla fält för likhet.

Notera: Testet är specifikt fokuserat på informationen du återger; i vårt exempel, det vill säga Counter komponent som har fått en initialCount stötta. Detta tyder på att även om en annan fil—säg, låt oss App.js—har saknade rekvisita i Counter komponenten kommer testet fortfarande att godkännas eftersom det enbart är inriktat på Counter.js och vet inte hur man använder Counter komponent. Dessutom, eftersom testerna är oberoende av varandra, påverkar det inte heller att rendera samma komponent med olika rekvisita i andra tester.

Nu kan vi köra uppsättningstestet:

$ yarn test

Testet bör misslyckas:

FAIL  src/Counter.test.js
  Counter
    × counter displays correct initial count (75 ms)

  ● Counter › counter displays correct initial count

    expect(received).toEqual(expected) // deep equality

    Expected: 0
    Received: "0"

       5 |   it("counter displays correct initial count", () => {
       6 |     render();
    >  7 |     expect(screen.getByTestId("count").textContent).toEqual(0);
         |                                                     ^
       8 |   });
       9 | });
      10 |

      at Object. (src/Counter.test.js:7:53)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.929 s, estimated 2 s
Ran all test suites related to changed files.

Detta test misslyckades eftersom vi hade testat ett nummer mot en sträng, vilket resulterade i en djupt jämställdhetsfel. För att fixa det, gjutas d textContent, dvs initialvärdet, i vår callback-funktion som ett nummer:


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
     
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });
});

Nu kommer vår kod att klara det första testet:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.271 s
Ran all test suites related to changed files.

Detta är ett enkelt exempel på hur man testar medan skrivlogik hjälper dig att undvika problem längre fram innan tekniska skulder ackumuleras ytterligare. Att testa i förtid kan också låsa in dig, eftersom omfaktorering och ändring av logik är dyrare tidsmässigt om du också måste skriva om tester.

Att hitta en bra balans kan hjälpa till att uppgradera din programvaras kvalitet, med en minimal negativ effekt på din produktivitet och hastighet.

Testa inkrementknappen

För att testa att increment knappen fungerar som den ska, det vill säga att öka count värde med ett varje gång det klickas, måste vi först komma åt increment knappen, så definierar vi en ny it() metod för detsamma.

Eftersom värdet på knappen inte är dynamiskt, det vill säga den kommer alltid att ha värdet Increment inuti den använder vi getByRole() metod istället för getByTestId() för att fråga DOM.

När du använder getByRole() metod, beskriver en roll ett HTML-element.

Vi måste också skicka in ett objekt för att definiera vilken knapp vi vill testa, eftersom det kan finnas många knappar när DOM renderas. I objektet sätter vi en name med ett värde som måste vara samma som texten på inkrementknappen.

Nästa sak att göra är att simulera en klickhändelse med hjälp av fireEvent() metod, som gör det möjligt att avfyra händelser som simulerar användaråtgärder under testning.

Först skriver vi ett test för att se om räknevärdet ökar med 1 från dess initiala värde på 0:


import { fireEvent, render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });

  it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue = Number(screen.getByTestId("count").textContent);
    expect(countValue).toEqual(1);
  });
});

Detta resulterar i:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (66 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.405 s
Ran all test suites related to changed files.

Sedan kan vi också skriva ett test för att kontrollera om count värdet var 0 innan knappen klickades genom att definiera två expect() metoder – en innan klickhändelsen aktiveras och en annan efter klickhändelsen:


it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    let countValue1 = Number(screen.getByTestId("count").textContent);
    expect(countValue1).toEqual(0);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue2 = Number(screen.getByTestId("count").textContent);
    expect(countValue2).toEqual(1);
});

Testerna klarade fortfarande:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (82 ms)
    √ count should increment by 1 if increment button is clicked (60 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.388 s
Ran all test suites related to changed files.

Testa minskningsknappen

På samma sätt skrev vi testet för Increment knappen definierar vi testet för Decrement knapp så här:


it("count should decrement by 1 if decrement button is clicked", () => {
  render(<Counter initialCount={0} />);
  fireEvent.click(screen.getByRole("button", { name: "Decrement" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(-1);
});

Detta resulterar i:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (73 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.346 s
Ran all test suites related to changed files.

Testar omstartsknappen

Liknande den Increment och Decrement knappar, definierar vi testet för Restart knapp så här:


it("count should reset to 0 if restart button is clicked", () => {
  render(<Counter initialCount={50} />);
  fireEvent.click(screen.getByRole("button", { name: "Restart" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(0);
});

För teständamål sattes det initiala värdet till 50 (godtyckligt värde), och när testet körs klarar alla fyra testerna framgångsrikt:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)
    √ count should increment by 1 if increment button is clicked (57 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (16 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.583 s
Ran all test suites related to changed files.

Testar Switch Sign-knappen

Vi skriver också testet för att invertera tecknet på count värde genom att ställa in värdet på count till 50 i testfilen. Håll sedan utkik efter vilket tecken som återges före och efter att en klickhändelse avfyras av knappen:


it("count invert signs if switch signs button is clicked", () => {
  render(<Counter initialCount={50} />);
  let countValue1 = Number(screen.getByTestId("count").textContent);
  expect(countValue1).toEqual(50);
  fireEvent.click(screen.getByRole("button", { name: "Switch signs" }));
  let countValue2 = Number(screen.getByTestId("count").textContent);
  expect(countValue2).toEqual(-50);
});

Detta resulterar i:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (91 ms)
    √ count should increment by 1 if increment button is clicked (72 ms)
    √ count should decrement by 1 if increment button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (19 ms)
    √ count invert signs if switch signs button is clicked (14 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        3.104 s
Ran all test suites related to changed files.

Wooshh! Alla tester har godkänts för vår räknarapp.

Att skriva tester är inte svårt – vi simulerar effektivt användningsfallen för en funktion för att säkerställa att den inte går sönder när den används på avsett sätt och som oavsiktligt. Tillhandahöll någon ett värde utanför gränserna? Fel format? Applikationen bör lösa problemet istället för att misslyckas.

Generellt sett är en bra utgångspunkt för att testa:

  • Testa för avsett beteende (oavsett vilka funktioner du har)
  • Testa för alla aspekter av oavsiktligt beteende (fel indata, till exempel format som inte stöds, gränser, etc.)
  • Testa numeriskt (om din funktion producerar numeriska värden som kan verifieras, beräkna resultatet för hand och kontrollera om det ger rätt utdata)

Bästa praxis för enhetstestning

  • Tester bör vara deterministiska: Att köra samma test på samma komponent flera gånger bör ge samma resultat varje gång. Du måste se till att dina genererade ögonblicksbilder inte innehåller plattformsspecifika eller andra icke-deterministiska data.

  • Undvik onödiga tester: Bra tester kommer inte med onödiga förväntningar eller testfall.
    Vi kan hitta en bättre förståelse genom att ta en titt på testerna nedan:

test('the success modal is visible', () => {});
test('the success modal has a success message', () => {});

Om vi ​​vet att framgångsmeddelandet i framgångsmodalen är synligt, betyder det att framgångsmodalen i sig också är synlig. Så i det här fallet kan vi säkert ta bort det första testet och bara utföra det andra, eller kombinera dem tillsammans. Att ha många tester kan ge en falsk känsla av säkerhet om de är överflödiga.

  • Undvik att avslöja intern logik: Om ditt test utför en åtgärd som din användare inte gör (som att testa en intern metod som inte är exponerad för användaren), testar du troligen implementeringsdetaljer. Det kan sluta med att du exponerar en privat funktion enbart för att testa din komponent. Detta är en kodlukt som bör undvikas. Omstrukturera istället din kodbas så att den privata funktionen är testbar utan att exponera den offentligt.

  • Undvik att testa implementeringsdetaljer: Om vår ansökan ökar x och y - huruvida x ökas först eller inte sannolikt har ingen betydelse, så länge resultatet är detsamma. Du bör alltid kunna refaktorera, ändra och på annat sätt uppdatera implementeringsdetaljer utan att bryta testerna, annars skulle tester katalysera teknisk skuldackumulering genom att öka kostnaderna för omfaktorisering och optimering.

  • Placera affärslogik i rena funktioner snarare än UI-komponenter.

Slutsats

Den här guiden handlar i första hand om enhetstestning. Det var dock viktigt att vi först förstod och uppskattade allt som omfattar testning, inklusive vad det innebär, metoder för testning, typer av testning och dess fördelar och nackdelar.

Det är viktigt att komma ihåg varför du skriver prov medan du skriver dem. Vanligtvis är målet med att skriva tester att spara tid. Tester ger utdelning om projektet du arbetar med är stabilt och kommer att utvecklas under lång tid. Med detta är det säkert att säga att att testa en applikation kan ses som inte värt det, om det inte sparar utvecklingstid. Framför allt är bra tester enkla att underhålla och ger förtroende när du ändrar din kod.

Vi lärde oss också hur man frågar DOM medan vi testade React-applikationer med hjälp av getByTestId() metod. Det är faktiskt användbart för att definiera behållare och fråga element med dynamisk text, men det bör inte vara din standardfråga. Istället för att använda getByTestId() metod direkt, prova en av dessa först:

  • getByRole() – den frågar efter ett element samtidigt som det säkerställer att det är tillgängligt med rätt roll och text
  • getByLabelText() – det är en utmärkt fråga för att interagera med formulärelement, den kontrollerar också att våra etiketter är korrekt länkade till våra indata via attributen for och id
  • getByText() – När ingen av de två föregående frågorna är tillgängliga, visas getByText() metod kommer att vara användbar för att komma åt element baserade på text som är synlig för användaren
  • getByPlaceholderText(): Den här frågan är mycket att föredra framför ett test-ID när allt du behöver för att fråga ett element är en platshållare.

Vi hoppas att den här guiden är till hjälp för dig! Du kan få tillgång till förvaret för den här guiden och leka med allt som finns där med hjälp av detta länk på GitHub.

Tidsstämpel:

Mer från Stackabuse