- React.js: Wprowadzenie do kursu od podstaw
- Poznaj React.js
- Pierwszy komponent w React.js
- Props czyli atrybuty w React.js
- Podział na komponenty w React.js
- Klasy jako komponenty React.js
- Interakcja z komponentami React.js
- Stan komponentów React.js
- State w React.js 2
- Metody cyklu życia komponentu w React.js
- React.js w przykładach: filtrowanie statycznej listy
- Tworzenie aplikacji React.js dzięki create-react-app
- React.js na GitHub Pages dzięki create-react-app
- Testowanie aplikacji React.js — podstawy Enzyme
- Testowanie React.js w Enzyme — props, state i interakcje
- Poprawne bindowanie funkcji w React.js
- Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
- Komunikacja pomiędzy komponentami w React.js
- Komunikacja z API w React.js
- Formularze w React.js — kontrolowane komponenty
- Formularze w React.js — niekontrolowane komponenty
- Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
- TDD w React.js z pomocą react-testing-library
- Flux i Redux: globalny store i jednokierunkowy przepływ danych
- React + Redux — kurs: wprowadzenie i podstawy
- React + Redux — filtrowanie listy, proste selektory
- Projektowanie komponentów: Presentational & Container Components
- Asynchroniczność w Redux: redux-thunk
- Kiedy używać state, a kiedy Redux?
- Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
- Leniwe ładowanie komponentów w React dzięki import
- Higher Order Reducers — Redux i powtarzanie kodu
- React Hooks — wprowadzenie i motywacja
- React Hooks: useState, czyli stan w komponentach funkcyjnych
- React Hooks: useState — wiele stanów, callbacki i inne niuanse
- React Hooks: useEffect — efekty uboczne w komponencie
- React Hooks a żądania do API
- useReducer — przenoszenie logiki poza komponent
- useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
- Wady React Hooks
- React Hooks: Piszemy własne hooki!
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 bibliotekaenzyme-adapter-react-16
— enzyme wymaga zainstalowania odpowiedniego adaptera do konkretnej wersji React.jsreact-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:
- Jeśli przekazano pustą tablicę to sprawdź czy wyświetla się komunikat, że nic nie znaleziono.
- Jeśli przekazano tablicę z imionami to sprawdź czy komunikatu już nie ma.
- 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ć 🙂
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 Sprawdź szkolenia z React! 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!
- React.js: Wprowadzenie do kursu od podstaw
- Poznaj React.js
- Pierwszy komponent w React.js
- Props czyli atrybuty w React.js
- Podział na komponenty w React.js
- Klasy jako komponenty React.js
- Interakcja z komponentami React.js
- Stan komponentów React.js
- State w React.js 2
- Metody cyklu życia komponentu w React.js
- React.js w przykładach: filtrowanie statycznej listy
- Tworzenie aplikacji React.js dzięki create-react-app
- React.js na GitHub Pages dzięki create-react-app
- Testowanie aplikacji React.js — podstawy Enzyme
- Testowanie React.js w Enzyme — props, state i interakcje
- Poprawne bindowanie funkcji w React.js
- Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
- Komunikacja pomiędzy komponentami w React.js
- Komunikacja z API w React.js
- Formularze w React.js — kontrolowane komponenty
- Formularze w React.js — niekontrolowane komponenty
- Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
- TDD w React.js z pomocą react-testing-library
- Flux i Redux: globalny store i jednokierunkowy przepływ danych
- React + Redux — kurs: wprowadzenie i podstawy
- React + Redux — filtrowanie listy, proste selektory
- Projektowanie komponentów: Presentational & Container Components
- Asynchroniczność w Redux: redux-thunk
- Kiedy używać state, a kiedy Redux?
- Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
- Leniwe ładowanie komponentów w React dzięki import
- Higher Order Reducers — Redux i powtarzanie kodu
- React Hooks — wprowadzenie i motywacja
- React Hooks: useState, czyli stan w komponentach funkcyjnych
- React Hooks: useState — wiele stanów, callbacki i inne niuanse
- React Hooks: useEffect — efekty uboczne w komponencie
- React Hooks a żądania do API
- useReducer — przenoszenie logiki poza komponent
- useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
- Wady React Hooks
- React Hooks: Piszemy własne hooki!
Brak linku do następnej części
Cache? 😛
chyba cache, bo teraz mam do 16 🙂
[…] istnieje wiele bibliotek, które rozwiązują nam problem renderowania komponentu (np. Enzyme), mockowania odpowiedzi z servera (np. MockAxios), ale często mają […]
Mogłeś od razu pod Travisa podpiąć repo 😉
Czy ma ktoś pomysł, dlaczego przy każdym teście wyskakuje mi „ReferenceError: UsersList is not defined”? Kod mam skopiowany żywcem z przykładu Michała.
A jak importujesz `UsersList` do testu?
https://github.com/mmiszy/typeofweb-kurs-react/blob/595085df6cc37f6287fb6b814b79bfb2336b1429/src/UsersList.test.js#L3
import App from './App'; I to raczej przechodzi, bo test z inputem jest passed
Błąd mówi konkretnie o `UsersList`, więc najprawdopodobniej zapomniałaś zaimportować ten komponent.
Ok, zadziałało, nie wiedziałam, że muszę wyodrębnić UsersList do nowego pliku (wszystko miałam w App.js). Dzięki za pomoc!
Michał, czemu używasz „it” zamiast „test”? W różnych źródłach wiedziałem i jedno i drugie. Z dokumentacji Jest wynika, że nie ma pomiędzy nimi różnicy, więc zastanawiam się, czemu używa się jednego lub drugiego? Dzięki.
Zwyczajowo w TDD/BDD używa się „describe” i „it” oraz zaczyna nazwy testów, aby testy czytały się jak ładne opowiadanie 😉
Describe: MyComponent
it should render
it should show a list of users
albo
Describe: MyComponent
it renders
it shows a list of users
dzięki. faktycznie czyta się to lepiej.
Hej. Michał. Na wstępie napiszę tylko, że odwalasz kawał świetnej roboty. Kurs jest naprawdę świetny. Mam pytanie odnośnie importowania componentów w testach. Próbując odpalić twoje testy zauważyłem, że przy mojej strukturze projektu importowanie wywalało testy. Po prostu moja aplikacja ma componenty rozdzielone na pliki Home.js oraz UserList.js. W moim przypadku poprawnie napisane testy to:
import React from 'react';
import App from './App';
import { shallow } from 'enzyme';
import UserList from './components/UserList';
import Home from './components/Home';
it('renders without crashing', () => {
shallow();
});
it('includes input', () => {
const app = shallow();
expect(app.containsMatchingElement()).toEqual(true)
});
it('includes UserList', () => {
const app = shallow();
expect(app.containsMatchingElement()).toEqual(true)
});
Da się jakoś wymusić, żeby aplikacja testowała componenty od najwyższego poziomu, schodząc w dół? Aplikacja a App.js wygląda tak:
import React, { Component } from 'react';
import Home from '../src/components/Home';
class App extends Component {
render() {
return (
);
}
}
export default App;
czyli teoretycznie input oraz UserList znajdują się w App (są one po prostu zagnieżdżone w Home.
Miałem błąd: „Cannot find module 'react' from 'ReactSixteenAdapter.js'”
Okazało się, że przyczyną były brakujące pozycje w package.json.
Warto zobaczyć, czy ma się wszystkie zależności z https://github.com/mmiszy/typeofweb-kurs-react/blob/part-2/package.json
Ja uzupełniłem:
„enzyme”: „3.3.0”,
„enzyme-adapter-react-16”: „1.1.1”,
„jest-enzyme”: „4.0.2”,
„react-test-renderer”: „16.12.0”
Samo npm install nie wystarczy
Mam problem z tym.
Pierwszy test przechodzi poprawnie
it('renders without crashing', () => {
shallow();
});
Drugi test nie przechodzi
it('includes input', () => {
const app = shallow();
expect(app.containsMatchingElement()).toEqual(true)
});
Komunikat:
… ale gdy zamienię `shallow` na `mount` to test jest ok.
Trzeci test nie przechodzi ani przy shallow ani przy mount
it('includes input', () => {
const app = mount();
expect(app.containsMatchingElement()).toEqual(true)
});
Komunikat:
Oczywiście podejrzewam, że muszę jakoś zaimportować UsersList, ale to tak na szybko piszę moje spostrzeżenie.
Odpowiem sam sobie. Może komuś się kiedyś przyda.
App.js:
export { UsersList };
export default App;
App.test.js:
import App from './lessons/App';
import { UsersList } from './lessons/App';
import UsersList from './App';
🙂
Chyba jednak nie o to chodziło 🙁