Narzędzia do testowania
Importowanie
import ReactTestUtils from 'react-dom/test-utils'; // ES6
var ReactTestUtils = require('react-dom/test-utils'); // ES5 z zainstalowanym npm
Ogó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-renderer
ró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.document
orazwindow.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
,which
itp.) należy przekazać jawnie, ponieważ React nie dodaje ich automatycznie.