Testowanie aplikacji React.js — podstawy Enzyme

Ten wpis jest 14 częścią z 37 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API

Testowanie aplikacji to rzecz ważna. Do tej pory jednak nie wspomniałem ani słowem o testowaniu React.js. Czas najwyższy to zrobić! I od razu wrzucam Cię na głęboką wodę — użyjesz React.js i Enzyme — przemiłej biblioteki do testowania komponentów.

O zaletach samego testowania nie muszę chyba pisać. Utrzymanie kodu, łatwiejsze dodawanie nowych funkcji, testy służące jako dokumentacja… bajka 😉 Dlatego teraz po prostu weźmiesz poprzedni przykład (filtrowanie listy) i napiszesz do niego testy jednostkowe. Zacznij od zainstalowania enzyme.

create-react-app domyślnie korzysta z biblioteki jest do testów. Tak też będę robił w tym wpisie. Ale pamiętaj, że enzyme działa również z innymi popularnymi bibliotekami np. mocha czy chai.

Enzyme

Enzyme to biblioteka do testowania komponentów React.js. Ułatwia tworzenie komponentów na potrzeby testów, odczytywanie oraz zmianę propsów i state, a także pozwala na asercje. Zgodnie z dokumentacją create-react-app aby zacząć używać cra razem z enzyme trzeba zainstalować:

npm install --save enzyme enzyme-adapter-react-16 react-test-renderer

Co robią poszczególne paczki?

  • enzyme — wspomniana biblioteka
  • enzyme-adapter-react-16 — enzyme wymaga zainstalowania odpowiedniego adaptera do konkretnej wersji React.js
  • react-test-renderer — renderowanie bez konieczności istnienia DOM

Następnie stwórz jeszcze jeden plik src/setupTests.js. Tam zawrzyj konfigurację wszystkich testów. W najprostszej wersji wygląda ona tak:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

W tym samym pliku możesz też dodać np. globalne mocki — jeśli Ci będą potrzebne. Albo biblioteki, z których chcesz korzystać w testach.

Pierwszy test w Enzyme

Najprostszy test z użyciem Enzyme będzie po prostu renderował komponent. Jeśli wszystko zadziała poprawnie — test zostanie zaliczony. Jeśli wystąpi wyjątek — test zakończy się niepowodzeniem. Zacznij od zaimportowania React, i App. Do tego potrzebna będzie Ci funkcja shallow z enzyme. Dlaczego akurat shallow? Kilka słów o tym za moment, a na razie test:

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('renders without crashing', () => {
  shallow(<App />);
});

W kodzie powyżej tworzony jest jeden test, który tylko renderuje komponent App. Proste, prawda? 🙂

Odpal teraz polecenie npm test. Twoje testy będą teraz automatycznie uruchamiane przy każdej zmianie w kodzie.

shallow, mount, render

W poprzednim teście skorzystałem z funkcji shallow. Ale są też inne: mount oraz render.

  • shallow — renderuje tylko ten komponent (bez komponentów w nim zagnieżdżonych). Idealny do testów jednostkowych, bo masz pewność, że przypadkiem nie sprawdzasz zachowań innych komponentów w środku. Szybki.
  • mount — komponent jest renderowany i montowany w DOM. Do testowania komponentów, które wchodzą w interakcje z DOM albo są udekorowane przez High Order Components
  • render — renderuje komponent do statycznego HTML-a

Najczęściej w testach jednostkowych będziesz korzystać z shallow.

Prawdziwy test

Dodaj dwa nowe testy. Sprawdź czy App zawiera input oraz czy App zawiera w sobie UsersList:

it('includes input', () => {
  const app = shallow(<App />);
  expect(app.containsMatchingElement(<input />)).toEqual(true)
});

it('includes UsersList', () => {
  const app = shallow(<App />);
  expect(app.containsMatchingElement(<UsersList />)).toEqual(true)
});

Wszystko przechodzi. Ale skąd tak naprawdę masz pewność, że Twój kod działa? Może to błąd w testach i one przechodzą zawsze? 😉 Spróbuj usunąć z komponentu App element input. Oto rezultat:

 FAIL  src/App.test.js
  ● includes input

    expect(received).toEqual(expected)
    
    Expected value to equal:
      true
    Received:
      false

Rzeczywiście, test pokazał, że w komponencie nie ma inputa! Czyli testy są poprawne 😉

Testy przekazywanych propsów

Teraz przetestuj komponent UsersList, którego wyświetlanie zależy od przekazanych propsów:

  1. Jeśli przekazano pustą tablicę to sprawdź czy wyświetla się komunikat, że nic nie znaleziono.
  2. Jeśli przekazano tablicę z imionami to sprawdź czy komunikatu już nie ma.
  3. Sprawdź czy dla każdego imienia jest wyrenderowany element na liście.

Nic prostszego 😉

it('shows message when there are no users', () => {
    const usersList = shallow(<UsersList users={[]} />);
    expect(usersList.text()).toContain('No results!')
});

it(`doesn't show message when there are users`, () => {
    const usersList = shallow(<UsersList users={['Michal']} />);
    expect(usersList.text()).not.toContain('No results!')
});

it(`shows a list of users`, () => {
    const users = ['Michal', 'Ania'];
    const usersList = shallow(<UsersList users={users} />);
    expect(usersList.find('li').length).toEqual(users.length);
});

describe('list of users', () => {
    const users = ['Michal', 'Ania'];
    const usersList = shallow(<UsersList users={users} />);
    
    users.forEach(user => {
        it(`includes name ${user} on the list`, () => {
            expect(usersList.containsMatchingElement(<li>{user}</li>)).toEqual(true)
        });
    });
});

Wszystkie napisane wyżej testy powinny bez problemu przechodzić 🙂

Testy React.js w Enzyme

Podsumowanie

W tej części zrobiłem tylko lekkie wprowadzenie do podstaw enzyme. Cały kod jest dostępny na moim GitHubie: https://github.com/mmiszy/typeofweb-kurs-react/tree/part-2 W kolejnym wpisie omówię jak testować zmiany propsów i stanu, a także jak przetestować interakcje z komponentami!

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie*:  Przetestuj czy komponent App przekazuje do komponentu UsersList tablicę z imionami. Kod dodaj w komentarzu!

Nawigacja po kursie:
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API