- 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!
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 elementuprop(key)
— zwraca prop o nazwiekey
state([key])
— zwraca cały state lub (opcjonalnie) to co kryje się pod kluczemkey
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 Poznaj React w dwa dni 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.
Ćwiczenie
Ćwiczenie: Przetestuj czy po skasowaniu literki „M” z inputa lista kontaktów wróci do stanu pierwotnego.
- 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!
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 😀
Niestety nie znam żadnych kursów JS 😉 Jedyne co mi przychodzi do głowy to seria książek „You Don’t Know JS”.
A masz jakieś konkretne pytania? 🙂
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.
1. Nie. Testy z tego artykułu działają poprawnie. Shallow renderuje tylko dany komponent, a więc propsy komponentów w nim zawartych można pobierać. Natomiast już ten zawarty komponent się nie wyrenderuje.
2. Nie powinny być, w kodzie musi być błąd.
3. Jak wyżej. Ale przy tak wklejonym kodzie znalezienie błędu jest praktycznie niemożliwe.
Jaka jest roznica pomiedzy
usersList.setProps({ users }) a usersList.setProps( users ); ? W momencie uzycia drugiej skladni testy nie przechodzą
W pierwszym przypadku jako argument przekazujesz coś co zawarłeś w zmiennej users, a w drugim przypadku przekazujesz obiekt {users}
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.
To się zgadza. Ale pytanie było o różnicę pomiędzy users a {users}, czyli pomiędzy users a {users: users}.
Niestety testy nie przechodzą.
Jakie testy, co próbowałeś zrobić? Na screenshocie jest tylko resultat, trudno na jego podstawie pomóc 🙂
Testy w repo bez zmian przechodzą
Hej
Mam problem w części z dodawaniem propsów.
Przy zapisie:
expect(usersList).toContainReact(
)
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.
Hej! To rzeczywiście nie jest jasne 🙂 W projekcie jest zainstalowany jeszcze
jest-enzyme
, który dodaje, między innymi, takie właśnie metody:https://github.com/FormidableLabs/enzyme-matchers/blob/master/README.md#tocontainreact
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)
W tym odcinku kursu nie ma jeszcze Promise (wszystko dzieje się synchronicznie), więc musiałbyś dokładnie pokazać co robisz.
Ale ogólnie, co do zasady, musiałbyś zamockować serwis, który dostarcza Ci dane.
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)
Jaka jest różnica między metodami onChange a onInput, używanych na inputach? W pierwszym przypadku testy z ostatniego przykładu nie przechodzą.
Nie ma żadnej różnicy — React celowo zmienił działanie onChange, żeby było praktycznie identyczne z onInput.
Tylko jak zmienię onInput na onChange, to w takim przypadku te testy nie przechodzą.
A jak wygląda kod komponentu UserList?
::
const UserList = ({ users }) => {
if (users.length > 0) {
return(
{users.map((user, userKey) =>- {user}
)}
)}
return(
No results
)
};
A FilterUsers? 🙂
Bardzo doceniam twoje rady. Zerknąłbyś na to jeszcze raz? Dałem screeny codu FilterUsers. 🙂
..
Nie miałem dostępu do internetu. Zaraz będzie.
No tak, ale skoro lista się filtruje dopiero po losowym czasie — to w jaki sposób uwzględniasz to w teście?
No właśnie. dzięki.
A FilterUsers? 🙂
TypeError: expect(...).toContainReact is not a function
Kod skopiowany kropka w kropkę, nie widzi mi tej metody 🙁