Narzędzia do testowania
Importowanie
import ReactTestUtils from 'react-dom/test-utils'; // ES6
var ReactTestUtils = require('react-dom/test-utils'); // ES5 z zainstalowanym npmOgólne informacje
ReactTestUtils pozwala na łatwiejsze testowanie komponentów reactowych przy pomocy dowolnego frameworka. W Facebooku korzystamy do tego celu z biblioteki Jest, która sprawia, że pisanie testów jest mniej kłopotliwe. Do nauki podstaw Jesta polecamy samouczek dla Reacta, znajdujący się na oficjalnej stronie biblioteki.
Uwaga:
Zalecamy korzystanie z biblioteki React Testing Library. Została ona stworzona w celu propagowania idei pisania testów, które używają komponentów podobnie jak potencjalny użytkownik aplikacji.
Dla Reacta w wersji 16 lub niższej polecamy bibliotekę Enzyme, dzięki której w łatwy sposób można pisać asercje, a także zmieniać i przechodzić drzewo zwrócone przez komponenty reactowe.
act()mockComponent()isElement()isElementOfType()isDOMComponent()isCompositeComponent()isCompositeComponentWithType()findAllInRenderedTree()scryRenderedDOMComponentsWithClass()findRenderedDOMComponentWithClass()scryRenderedDOMComponentsWithTag()findRenderedDOMComponentWithTag()scryRenderedComponentsWithType()findRenderedComponentWithType()renderIntoDocument()Simulate
Dokumentacja
act()
Aby przygotować dany komponent do testowania, należy renderujący i aktualizujący go kod “opakować” w wywołanie funkcji act(). Dzięki temu test zostanie uruchomiony w taki sposób, aby jak najwierniej odtworzyć zachowanie Reacta w przeglądarce.
Uwaga
Biblioteka
react-test-rendererrównież udostępnia funkcjęact, która działa w podobny sposób.
Dla przykładu, załóżmy, że napisaliśmy następujący komponent Counter (pol. licznik):
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
document.title = `Kliknięto ${this.state.count} razy`;
}
componentDidUpdate() {
document.title = `Kliknięto ${this.state.count} razy`;
}
handleClick() {
this.setState(state => ({
count: state.count + 1,
}));
}
render() {
return (
<div>
<p>Kliknięto {this.state.count} razy</p>
<button onClick={this.handleClick}>
Kliknij mnie
</button>
</div>
);
}
}W taki oto sposób moglibyśmy go przetestować:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('potrafi wyrenderować i aktualizować licznik', () => {
// Testuje pierwsze renderowanie i metodę cyklu życia "componentDidMount"
act(() => { ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('Kliknięto 0 razy');
expect(document.title).toBe('Kliknięto 0 razy');
// Testuje drugie renderowanie i metodę cyklu życia "componentDidUpdate"
act(() => { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(label.textContent).toBe('Kliknięto 1 razy');
expect(document.title).toBe('Kliknięto 1 razy');
});Zwróć uwagę, że przesyłanie zdarzeń DOM działa tylko wtedy, gdy kontener jest umieszczony w document. Aby uniknąć powtarzania szablonowego kodu, możesz użyć biblioteki pomocniczej, jak na przykład React Testing Library.
W przykładach znajdziesz więcej szczegółów na temat zachowania funkcji act(), jak również przykłady jej użycia.
mockComponent()
mockComponent(
componentClass,
[mockTagName]
)Jeśli przekażesz do tej funkcji atrapę komponentu (ang. mocked component), zostanie ona wzbogacona o przydatne funkcje, które pozwolą na traktowanie jej jak sztucznego komponentu reactowego. Zamiast wyrenderować się zgodnie z implementacją, komponent stanie się zwykłym elementem <div> (lub innym, jeśli podamy wartość w parametrze mockTagName) renderującym przekazanych potomków.
Uwaga:
Funkcja
mockComponent()jest przestarzała. Zamiast niej zalecamy używanie funkcjijest.mock().
isElement()
isElement(element)Zwraca true, jeśli argument element jest elementem reactowym.
isElementOfType()
isElementOfType(
element,
componentClass
)Zwraca true, jeśli argument element jest elementem reactowym o klasie podanej jako componentClass.
isDOMComponent()
isDOMComponent(instance)Zwraca true, jeśli argument instance jest standardowym komponentem DOM (np. <div> lub <span>).
isCompositeComponent()
isCompositeComponent(instance)Zwraca true, jeśli argument instance jest komponentem użytkownika, typu klasowego lub funkcyjnego.
isCompositeComponentWithType()
isCompositeComponentWithType(
instance,
componentClass
)Zwraca true, jeśli argument instance jest komponentem o klasie podanej jako componentClass.
findAllInRenderedTree()
findAllInRenderedTree(
tree,
test
)Przeszukuje wszystkie komponenty w drzewie tree i zwraca te, dla których wywołanie funkcji test(komponent) daje true. Funkcja ta sama w sobie nie jest zbyt użyteczna, jednak jest podstawą dla innych narzędzi do testowania.
scryRenderedDOMComponentsWithClass()
scryRenderedDOMComponentsWithClass(
tree,
className
)Wyszukuje wszystkie elementy DOM w wyrenderowanym drzewie, których nazwa klasy CSS odpowiada wartości argumentu className.
findRenderedDOMComponentWithClass()
findRenderedDOMComponentWithClass(
tree,
className
)Podobna w działaniu do scryRenderedDOMComponentsWithClass(), lecz oczekuje dokładnie jednego wyniku. W przypadku znalezienia innej liczby elementów rzuca wyjątek.
scryRenderedDOMComponentsWithTag()
scryRenderedDOMComponentsWithTag(
tree,
tagName
)Wyszukuje wszystkie elementy DOM w wyrenderowanym drzewie, których nazwa znacznika pasuje do wartości argumentu tagName.
findRenderedDOMComponentWithTag()
findRenderedDOMComponentWithTag(
tree,
tagName
)Podobna w działaniu do scryRenderedDOMComponentsWithTag(), lecz oczekuje dokładnie jednego wyniku. W przypadku znalezienia innej liczby elementów rzuca wyjątek.
scryRenderedComponentsWithType()
scryRenderedComponentsWithType(
tree,
componentClass
)Wyszukuje wszystkie instancje komponentów, których typ jest równy argumentowi componentClass.
findRenderedComponentWithType()
findRenderedComponentWithType(
tree,
componentClass
)Podobna w działaniu do scryRenderedComponentsWithType(), lecz oczekuje dokładnie jednego wyniku. W przypadku znalezienia innej liczby elementów rzuca wyjątek.
renderIntoDocument()
renderIntoDocument(element)Renderuje element reactowy do stworzonego w locie węzła drzewa DOM. Ta funkcja działa tylko na drzewie DOM w ramach dokumentu. Daje ten sam rezultat, co:
const domContainer = document.createElement('div');
ReactDOM.createRoot(domContainer).render(element);Uwaga:
Zanim zaimportujesz bibliotekę React w kodzie, w globalnym zakresie muszą być dostępne zmienne
window,window.documentorazwindow.document.createElement. W przeciwnym wypadku React będzie “myślał”, że nie ma dostępu do drzewa DOM, co spowoduje wyłączenie niektórych funkcji, np.setState.
Inne narzędzia
Simulate
Simulate.{eventName}(
element,
[eventData]
)Symuluje przesłanie zdarzenia do węzła DOM, opcjonalnie dodając do niego dane zawarte w argumencie eventData.
Obiekt Simulate posiada odpowiednie metody dla każdego ze zdarzeń obsługiwanego przez Reacta.
Kliknięcie w element
// <button ref={(node) => this.button = node}>...</button>
const node = this.button;
ReactTestUtils.Simulate.click(node);Zmiana wartości pola i wciśnięcie klawisza ENTER.
// <input ref={(node) => this.textInput = node} />
const node = this.textInput;
node.value = 'żyrafa';
ReactTestUtils.Simulate.change(node);
ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13});Uwaga
Wszelkie właściwości dla zdarzenia (np.
keyCode,whichitp.) należy przekazać jawnie, ponieważ React nie dodaje ich automatycznie.