Тестирование компонентов React с помощью асинхронных методов - PullRequest
0 голосов
/ 13 мая 2018

У меня есть компонент, который ведет себя следующим образом.

  1. Визуализация с отображением 'Загрузка'.
  2. Получение некоторых данных.
  3. После загрузки,Заполните состояние.
  4. Повторите, показывая, что данные загружены.

Код выглядит так:

import React from 'react';

class IpAddress extends React.Component {
  state = {
    ipAddress: null
  };

  constructor(props) {
    super(props);

    this.fetchData();
  }

  fetchData() {
    return fetch(`https://jsonip.com`)
      .then((response) => response.json())
      .then((json) => {
        this.setState({ ipAddress: json.ip });
      });
  }

  render() {
    if (!this.state.ipAddress) return <p class="Loading">Loading...</p>;

    return <p>Pretty fly IP address you have there.</p>
  }
}

export default IpAddress;

Это отлично работает.Шутка - это борьба.Использование jest-fetch-mock работает хорошо.

import React from 'react';
import ReactDOM from 'react-dom';
import { mount } from 'enzyme';

import IpAddress from './IpAddress';

it ('updates the text when an ip address has loaded', async () => {
  fetch.mockResponse('{ "ip": "some-ip" }');

  const address = mount(<IpAddress />);
  await address.instance().fetchData();

  address.update();

  expect(address.text()).toEqual("Pretty fly IP address you have there.");
});

Немного грустно, что мне приходится звонить await address.instance().fetchData(), просто чтобы убедиться, что обновление произошло.Без этого обещание от fetch или асинхронная природа setState (я не уверен, что) не будут выполняться до тех пор, пока не произойдет мой expect;текст оставлен как «Загрузка».

Это нормальный способ проверки кода, подобного этому?Вы бы написали этот код совершенно по-другому?

Моя проблема с тех пор обострилась.Я использую компонент высокого порядка , что означает, что я больше не могу делать .instance() и использовать методы на нем - я не уверен, как вернуться к моему развернутому IpAddress.Использование IpAddress.wrappedComponent не возвращает мне исходный IpAddress, как я и ожидал.

Это происходит со следующим сообщением об ошибке, которое, к сожалению, я не понимаю.

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `WrapperComponent`.

Ответы [ 2 ]

0 голосов
/ 24 мая 2019

Вы можете использовать реагирующее-тестирование-библиотеки waitForElement, чтобы избежать необходимости явно await при вызове fetch и немного упростить вещи:

import React from "react";
import IpAddress from "./IpAddress";
import { render, cleanup, waitForElement } from "react-testing-library";

// So we can use `toHaveTextContent` in our expectations.
import "jest-dom/extend-expect";

describe("IpAddress", () => {
  beforeEach(() => {
    fetch.resetMocks();
  });

  afterEach(cleanup);

  it("updates the text when an IP address has loaded", async () => {
    fetch.mockResponseOnce(JSON.stringify({ ip: "1.2.3.4" }));

    const { getByTestId } = render(<IpAddress />);

    // If you add data-testid="ip" to your <p> in the component.
    const ipNode = await waitForElement(() => getByTestId("ip"));

    expect(ipNode).toHaveTextContent("Pretty fly IP address you have there.");
  });
});

Это автоматически подождет, пока ваш элемент появится и потерпит неудачу, если он не появится по истечении некоторого времени ожидания. Вам все еще нужно await, но, надеюсь, это немного ближе к тому, что вы изначально хотели.

0 голосов
/ 14 мая 2018

Должен признать, что раньше я не использовал jest-fetch-mock, но из документов и моих маленьких экспериментов похоже, что он заменяет глобальный fetch надуманной версией. Обратите внимание, что в этом примере не ожидаются какие-либо обещания: https://github.com/jefflau/jest-fetch-mock#simple-mock-and-assert. Это просто проверка того, что fetch был вызван с правильными аргументами. Поэтому я думаю, что вы можете удалить async / await и утверждать, что есть вызов jsonip.com.

То, что я думаю, чтобы сбить вас с толку, на самом деле - это жизненный цикл React. По сути, это сводится к тому, где вы делаете вызов fetch. Команда React отговаривает вас ставить «побочные эффекты» (например, fetch) в constructor. Вот официальное описание документов React: https://reactjs.org/docs/react-component.html#constructor. К сожалению, я не нашел хорошей документации по , почему . Я полагаю, это потому, что React может вызывать constructor в нечетные моменты в течение жизненного цикла. Я думаю, что это также причина, по которой вам приходится вручную вызывать функцию fetchData в вашем тесте.

Лучшая практика для устранения побочных эффектов - componentDidMount. Вот правильное объяснение того, почему: https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/ (хотя стоит отметить, что * * * * componentWillMount теперь не рекомендуется в Реакте 16.2). componentDidMount вызывается ровно один раз, только после рендеринга компонента в DOM.

Стоит также отметить, что все это скоро изменится в следующих версиях React. Это сообщение в блоге / видео конференции содержит гораздо больше деталей: https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Этот подход означает, что он будет отображаться изначально в состоянии загрузки, но как только запрос будет решен, вы можете запустить повторную визуализацию, установив состояние. Поскольку в своем тесте вы используете mount от Enzyme, при этом будут вызваны все необходимые методы жизненного цикла, включая componentDidMount, поэтому вы должны увидеть, как вызывается смоделированный fetch.

Что касается компонента более высокого порядка, я иногда использую уловку, которая, возможно, не лучшая практика, но я думаю, что это довольно полезный хак. Модули ES6 имеют один экспорт default, а также столько «обычных» экспортов, сколько вам нужно. Я использую это, чтобы экспортировать компонент несколько раз.

Соглашение React заключается в использовании экспорта default при импорте компонентов (т.е. import MyComponent from './my-component'). Это означает, что вы можете экспортировать другие файлы из файла.

Мой трюк заключается в export default компоненте, обернутом HOC, так что вы можете использовать его в своих исходных файлах так же, как и с любым другим компонентом, но также экспортирует развернутый компонент как "обычный" " составная часть. Это будет выглядеть примерно так:

export class MyComponent extends React.Component {
  ...
}

export default myHOCFunction()(MyComponent)

Затем вы можете импортировать упакованный компонент с помощью:

import MyComponent from './my-component'

И развернутый компонент (т.е. для использования в тестах) с:

import { MyComponent } from './my-component'

Это не самый явный шаблон в мире, но это довольно эргономичный imo. Если вам нужна ясность, вы можете сделать что-то вроде:

export const WrappedMyComponent = myHOCFunction()(MyComponent)
export const UnwrappedMyComponent = MyComponent
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...