Testowanie React.js w Enzyme — props, state i interakcje

Ten wpis jest 15 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu 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: globalny store i jednokierunkowy przepływ danych
  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
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Pokazałem już jak pisać proste testy do aplikacji React.js z użyciem Enzyme. Sprawdzanie czy coś się renderuje, czy zawiera tekst, czy dobrze pokazuje elementy… W tym wpisie idę o krok dalej. Pokazuję jak w Enzyme testować interakcje z komponentami, odczytywać i zmieniać propsy a także state.

Pobieranie i ustawianie propsów oraz state

Na elementach (selektorach) Enzyme można wywołać kilka ciekawych metod. W tym momencie interesują Cię te służące do odczytywania i ustawiania propsów i stanu:

  • props() — zwraca wszystkie propsy danego elementu
  • prop(key) — zwraca prop o nazwie key
  • state([key]) — zwraca cały state lub (opcjonalnie) to co kryje się pod kluczem key
  • setProps(nextProps) — ustawia propsy komponentu (podany obiekt zostanie połączony z istniejącymi już propsami)
  • setState(nextState) — ustawia state komponentu (j.w.)

A więc przetestujmy teraz czy, w naszej ulubionej liście kontaktów, komponent <App> przekazuje do <UsersList> prop o nazwie users z tablicą imion:

it('passes all users to the UsersList', () => {
  const app = shallow(<App />);
  expect(app.find('UsersList').prop('users')).toEqual(['Michal', 'Kasia', 'Jacek', 'Marta', 'Tomek', 'Ania']);
})

Po kolei: Płytkie renderowanie komponentu App. Następnie znajdź komponent UsersList, pobierz jego prop o nazwie users i porównaj z podaną tablicą.

Następnie można by się pokusić o przetestowanie czy komponent UsersList poprawnie reaguje na zmianę propsów. A więc pierwszy render, a następnie zmiana propsów i test (jest to trochę test na siłę, ale to w tym momencie nieistotne):

describe('change props', () => {
    const users = ['Jan', 'Maria'];
    const usersList = shallow(<UsersList users={['Ktoś tam', 'Nieważne']} />);
    usersList.setProps({ users });
    
    users.forEach(user => {
        it(`includes name ${user} on the list`, () => {
            expect(usersList).toContainReact(<li>{user}</li>)
        });
    });
});

W analogiczny sposób można pobierać i ustawiać state, a następnie sprawdzać czy komponent się poprawnie renderuje.

Interakcje

Jedyny brakujący element układanki to testowanie interakcji z komponentami. W tym przykładzie chcesz sprawdzić czy wpisywanie czegoś w pole tekstowe powoduje rzeczywiście filtrowanie tablicy z imionami.

W bardziej realnym przykładzie zamockowałbym najpierw źródło danych, aby mieć 100% kontrolę nad przebiegiem testu. Tutaj ten krok pomijam i na razie zakładam, że lista imion jest mi po prostu znana i niezmienna.

Przyda się tutaj funkcja simulate z Enzyme. Przyjmuje ona dwa argumenty:

  • nazwę zdarzenia np. click albo input
  • mock obiektu zdarzenia (opcjonalnie)

W tym przypadku chcę przetestować wpisanie literki „M”. Muszę więc zasymulować zdarzenie input i podać obiekt zdarzenia z ustawionym value na wartość "M":

it('filters names on input', () => {
  const app = shallow(<App />);
  expect(app.find('UsersList').prop('users')).toEqual(['Michal', 'Kasia', 'Jacek', 'Marta', 'Tomek', 'Ania']);

  app.find('input').simulate('input', {currentTarget: {value: 'M'}})
  expect(app.find('UsersList').prop('users')).toEqual(['Michal', 'Marta', 'Tomek']);
});

Po kolei: Renderuje się aplikacja. Następnie upewniam się, że lista kontaktów jest taka, jak mi się wydaje, że jest (to krok w sumie zbędny). Następnie symuluję zdarzenie input i ponownie sprawdzam listę kontaktów — teraz jest już inna.

Podobnie można testować inne zdarzenia, np. click czy nawet focus.

Podsumowanie

Tym sposobem masz już pełen zestaw narzędzi potrzebny do testowania nawet najbardziej rozbudowanych komponentów. Enzyme jest świetną biblioteką i warto zapoznać się z jego pełną dokumentacją! Cały kod jest dostępny na moim GitHubie: https://github.com/mmiszy/typeofweb-kurs-react/tree/part-3

Jeśli chcesz poznać Enzyme i inne metody testowania React dogłębnie, to Naucz się React na szkoleniu!

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.

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Ćwiczenie

Ćwiczenie:  Przetestuj czy po skasowaniu literki „M” z inputa lista kontaktów wróci do stanu pierwotnego.

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu 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: globalny store i jednokierunkowy przepływ danych
  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
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
32 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
kensshin985
kensshin985
2 lat temu

Hej, dobra robota z tym kursem.
Pierwsze kilka rozdziałów było krótko, konkretnie wytłumaczone – co zachęcało do przerabiania kolejnych. Lecz jakoś od 10 się pogubiłem – wyszły prawdopodobnie moje braki w js.
Ogólnie bardziej interesuje mnie back-end(java/spring/hibernate), aczkolwiek w celach pisania pracy inżynierskiej oraz ogólnie nauczenia się czegoś nowego chciałbym liznąć trochę front’u. Przerobiłem książkę head first javascript ale nie czuję się po niej zbyt pewnie. Może mógłby mi ktoś polecić jakiś dobry kurs/blog traktujący o js.

Jeszcze jedna rzecz, którą zauważyłem. Z rozdziału 1 oraz 14(a może i innych) nie wyświetla się w nawigacji kursu rozdział 15 – prawdopodobnie wkradł się jakiś błąd 😀

Przemek
Przemek
2 lat temu

Hej,

mam pewien problem z testem interakcji.
1. Czy zamiast shallow nie powinniśmy użyć render? Sprawdzamy propsy wewnętrznego komponentu, a wedle tego co pisałeś w kursie shallow stworzy tylko wskazanego node bez jego dzieci.

2. Po zrobieniu render’a propsy w elemencie wewnętrznym są undefined.

3. Po wcięciu sprawdzenia propsów na starcie nie działa mi symulate: TypeError: listFilter.find(…).simulate is not a function co jest wynikiem tego że ‚UserList’ nie został znaleziony.

kod testu

it('filters names on input', () => {
const listFilter = render();
expect(listFilter.find('UsersList').prop('users')).toEqual(['Michal', 'Kasia', 'Jacek', 'Marta', 'Tomek', 'Ania']);

listFilter.find('input').simulate('input', {currentTarget: {value: 'M'}})
expect(listFilter.find('UsersList').prop('users')).toEqual(['Michal', 'Marta', 'Tomek']);
});

kod komponentu:

import React from 'react';

const allUsers = ['Michal', 'Kasia', 'Jacek', 'Marta', 'Tomek', 'Ania'];

export default class ListWithFilter extends React.Component {
constructor() {
super();

this.state = {
filteredUsers: allUsers
};
}

filterUsers = (e) => {
const text = e.currentTarget.value;
this.getFilteredUsersForText(text).then(filteredUsers =>
this.setState({
filteredUsers
})
).catch(error => console.log(error))
}

getFilteredUsersForText(text) {
return new Promise(resolve => {
const time = (Math.random() + 1) * 250;
setTimeout(() => {
const filteredUsers = allUsers.filter(user => user.toLowerCase().includes(text.toLowerCase()));
resolve(filteredUsers);
}, time);
});
}

render() {
return (


);
}
};

export const UsersList = ({users}) => {
if (users.length > 0) {
return (

    {users.map(user =>

  • {user}
  • )}

);
}

return (

No results!

);
};

Przeniosłem wewnętrznego const’a do osobnego pliku co rozwiązuje problem 2 i 3. Co prawda nie rozumiem dlaczego (jak i całej reszty JS’a 🙂 ).

Teraz moje filtrowanie w teście nie działa. Komponent wrzucone na strone jest za to funkcjonalny więc problem jest z testem, nie z kodem…

Niestety więcej pomysłów nie ma, a trzeba się powoli zbierać do pracy :).

p.s. Bardzo przejrzysty kursik i ogólnie artykuły. Świetna robota :). Jest szansa że między innymi dzięki Tobie opanuje JavaScript i z backend’u Javowewgo przejdę na fullstack xd.

crisu
crisu
1 rok temu

Jaka jest roznica pomiedzy

usersList.setProps({ users }) a usersList.setProps( users ); ? W momencie uzycia drugiej skladni testy nie przechodzą

Krzysztof Dziekiewicz
Krzysztof Dziekiewicz
9 miesięcy temu

Nie do końca zrozumiałem odpowiedzi, ale generalnie {users} jest skrótem, który jest automatycznie rozwijany do {users: users}. Oczywiście, jeśli w props oczekujemy np. nazwy „list”, to wtedy trzeba wywołać: setProps({list: users}).
Czyli skrót można użyć, jeśli nazwa własności jest taka sama, jak nazwa podawanej zmiennej.

BartusZak
1 rok temu

Niestety testy nie przechodzą.comment image

Agnieszka Kowalczyk
Agnieszka Kowalczyk
1 rok temu

Hej
Mam problem w części z dodawaniem propsów.

Przy zapisie:
expect(usersList).toContainReact(

  • {user}
  • )
    dostaję błąd:
    TypeError: expect(…).toContainReact is not a function

    Czytam w tej dokumentacji:
    http://airbnb.io/enzyme/docs/api/shallow.html
    i nie znajduję metody toContainReact

    Poproszę o pomoc.

    TTT
    TTT
    1 rok temu

    Hej, a jak jest z asynchronicznością? Jak chce przetestować
    it(‚should contain filtered names’ () => {
    userListContainer.find(‚Input’).simulate(‚input’, { currentTarget: { value: ‚M’ } });
    expect(userListContainer.find(‚UsersList’).prop(‚users’)).toEqual([‚Michal’, ‚Marta’, ‚Tomek’]);

    test się failuje. Jeżeli dodam tę asercję w timeout przechodzi. dziwie się trochę, że u Ciebie jest ok.

    Ja co prawda jednocześnie uczę się z kilku źródeł i mam pewnie inną strukturę komponentów (bardziej rozbite i nie trzymam większości rzeczy w App). Nie mniej timeout nie jest chyba najlepszą praktyką. Proszę o radę.

    metoda render z userListContainer (ktory to dopiero ląduje w App):

    render() {
    return (


    );
    używam też Promise (analogicznie jak w kursie)

    TTT
    TTT
    1 rok temu

    OJJAAA faktycznie. W poprzedniej lekcji dałeś zadanie na wprowadzenie asynchroniczności. Teraz zauważyłem, że w kodzie jaki podajesz używasz wersji synchronicznej. Także sorry, jak wspomniałem robie trochę po swojemu. Mam nadzieję, że będzie o testowaniu aynchornicznym bo ciężko mi to jakoś przyszło. Jako, że jestem QA to sam temat mockowania jest też dla mnie ciekawy. Byłbym wdzięczny za przykład asynchroniczność + mockowanie (real life example)

    Agnieszka Sołtysik
    Agnieszka Sołtysik
    1 rok temu

    Jaka jest różnica między metodami onChange a onInput, używanych na inputach? W pierwszym przypadku testy z ostatniego przykładu nie przechodzą.

    Agnieszka Sołtysik
    Agnieszka Sołtysik
    1 rok temu

    Tylko jak zmienię onInput na onChange, to w takim przypadku te testy nie przechodzą.

    Jurko
    Jurko
    1 rok temu

    comment image comment image Cześć. Nie przechodzi mi ostatnie test. Już kombinowałem jak mogłem. Komponent otrzymuje propsy, bo pierwszy expect przechodzi, ale później nie działa symulacja.

    Jurko
    Jurko
    1 rok temu

    comment image

    Jurko
    Jurko
    1 rok temu

    ::

    Jurko
    Jurko
    1 rok temu

    const UserList = ({ users }) => {
    if (users.length > 0) {
    return(

      {users.map((user, userKey) =>

    • {user}
    • )}

    )}
    return(

    No results

    )
    };

    Jurko
    Jurko
    1 rok temu

    Bardzo doceniam twoje rady. Zerknąłbyś na to jeszcze raz? Dałem screeny codu FilterUsers. 🙂

    Jurko
    Jurko
    1 rok temu

    ..

    Jurko
    Jurko
    1 rok temu

    Nie miałem dostępu do internetu. Zaraz będzie.

    Jurko
    Jurko
    1 rok temu
    Reply to  Jurko

    comment image comment image

    Jurko
    Jurko
    1 rok temu

    No właśnie. dzięki.

    Łukasz Parysek
    Łukasz Parysek
    1 rok temu

    TypeError: expect(...).toContainReact is not a function

    Kod skopiowany kropka w kropkę, nie widzi mi tej metody 🙁