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.

Jako alternatywę, firma Airbnb opublikowała narzędzie do testowania o nazwie Enzyme, które pozwala na łatwe pisanie asercji, a także manipulowanie i przechodzenie drzewa zwróconego przez komponenty reactowe.

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';
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.render(<Counter />, container);
  });
  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 funkcji jest.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.render(element, domContainer);

Uwaga:

Zanim zaimportujesz bibliotekę React w kodzie, w globalnym zakresie muszą być dostępne zmienne window, window.document oraz window.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.