Как протестировать компонент React с асинхронными функциями с помощью jest & энзима - PullRequest
0 голосов
/ 30 октября 2019

У меня есть компонент:

RandomGif.js

import React, { Component } from "react";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";

class RandomGif extends Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);
  }

  state = {
    loading: false,
    gif: null
  };

  componentDidMount() {
    this.handleClick();
  }

  async handleClick() {
    let gifContent = null;

    try {
      this.setState({
        loading: true
      });

      const result = await fetchRandom();

      if (!!result && result.data) {
        gifContent = {
          id: result.data.id,
          imageUrl: result.data.images.downsized_large.url,
          staticImageUrl: result.data.images.downsized_still.url,
          title: result.data.title
        };
      }
    } catch (e) {
      console.error(e);
    } finally {
      this.setState({
        loading: false,
        gif: gifContent
      });
    }
  }

  render() {
    const { gif, loading } = this.state;

    const showResults = gif && !loading;

    return (
      <div className="random">
        {!showResults && <Loader />}

        <button className="btn" onClick={this.handleClick}>
          RANDOMISE
        </button>

        {showResults && <Gif data={gif} />}
      </div>
    );
  }
}

export default RandomGif;

Если я вызываю методы непосредственно из экземпляра этого компонента, я могу успешно проверить, чтосостояние обновляется. Однако, если я имитирую нажатие кнопки, ничего не обновляется и тест не проходит. Я пробовал setImmediate и setTimeout трюки, но они не работают.

До сих пор я не смог написать контрольный пример для:

  • Имитация нажатия кнопки.
  • Имитация метода жизненного цикла.

Это то, что я до сих пор придумал.

RandomGif.spec.js

import React from "react";
import { shallow, mount } from "enzyme";
import RandomGif from "./RandomGif";

describe("Generate Random Gif", () => {
  it("should render correctly.", () => {
    const wrapper = shallow(<RandomGif />);

    expect(wrapper).toMatchSnapshot();
  });

  it("should load a random GIF on calling handleSearch fn.", async () => {
    const wrapper = mount(<RandomGif />);
    const instance = wrapper.instance();

    expect(wrapper.state("gif")).toBe(null);

    await instance.handleClick();

    expect(wrapper.state("gif")).not.toBe(null);
  });

  it("THIS TEST FAILS!!!", () => {
    const wrapper = mount(<RandomGif />);

    expect(wrapper.state("gif")).toBe(null);

    wrapper.find('button').simulate('click');
    wrapper.update()

    expect(wrapper.state("gif")).not.toBe(null);
  });
});

api.py

export const fetchRandom = async () => {
  const url = `some_url`;

  try {
    const response = await fetch(url);

    return await response.json();
  } catch (e) {
    console.error(e);
  }

  return null;
};

Пожалуйста, помогите мне найти недостающие фрагменты головоломки под названием «тестирование интерфейса».

1 Ответ

1 голос
/ 30 октября 2019
  1. Нам нужно макет fetchRandom, поэтому во время тестирования реальный запрос не будет отправлен.
import { fetchRandom } from "../resources/api";

jest.mock("../resources/api"); // due to automocking fetchRandom is jest.fn()

// somewhere in the it()
fetchRandom.mockReturnValue(Promise.resolve({ data: { images: ..., title: ..., id: ...} }))
Поскольку mocking - это Обещание (разрешено, но все еще обещано), нам нужно либо setTimeout, либо await <anything>, чтобы код компонента понял, что это Обещание разрешено. Это все о очереди микрозадач / макрозадач .
wrapper.find('button').simulate('click');
await Promise.resolve();
// component has already been updated here

или

it("test something" , (done) => {
wrapper.find('button').simulate('click');
setTimeout(() => {
  // do our checks on updated component
  done();  
}); // 0 by default, but it still works

})

Кстати, вы уже сделали это с

await instance.handleClick();

, но для меня это выглядит так же, как сказать

await 42;

И, кроме того, это работает (см. Ссылку на микрозадачи / макрозадачи). Я считаю, что это ухудшит читаемость тестов («что возвращает handleClick, что нам нужно ожидать?»). Поэтому я предлагаю использовать громоздкие, но менее запутанные await Promise.resolve(); или даже await undefined;

Обращение к state и непосредственный вызов методов экземпляра являются анти-шаблонами. Просто цитата ( Кент С. Доддс , с которой я полностью согласен):

В итоге, если ваш тест использует instance () или state (), знайте, чтовы тестируете вещи, о которых пользователь, возможно, даже не подозревал или даже заботился о них, что еще больше расширит ваши тесты и даст вам уверенность в том, что все будет работать, когда ваш пользователь их использует.

Давайте проверимвместо этого результат рендеринга:

import Loader from "./library/Loader";

...
wrapper.find('button').simulate('click');
expect(wrapper.find(Loader)).toHaveLength(1);
await Promise.resolve();
expect(wrapper.find(Loader)).toHaveLength(1);
expect(wrapper.find(Gif).prop("data")).toEqual(data_we_mocked_in_mock)

Давайте получим это в целом:

import {shallow} from "enzyme";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";

jest.mock( "../resources/api");

const someMockForFetchRandom = { data: { id: ..., images: ..., title: ... }};

it("shows loader while loading", async () => {
  fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
  const wrapper = shallow(<RandomGif />);
  expect(wrapper.find(Loader)).toHaveLength(0);
  wrapper.find('button').simulate('click');
  expect(wrapper.find(Loader)).toHaveLength(1);
  await Promise.resolve();
  expect(wrapper.find(Loader)).toHaveLength(0);
});

it("renders images up to response", async () => {
  fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
  const wrapper = shallow(<RandomGif />);
  wrapper.find('button').simulate('click');
  expect(wrapper.find(Gif)).toHaveLength(0);
  await Promise.resolve();
  expect(wrapper.find(Gif).props()).toEqual( {
    id: someMockForFetchRandom.data.id,
    imageUrl: someMockForFetchRandom.data.images.downsized_large.url,
    staticImageUrl: someMockForFetchRandom.data.images.downsized_still.url,
    title: someMockForFetchRandom.data.title
  });
});

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...