【问题标题】:How to test a React component having async functions with jest & enzyme如何使用 jest 和酶测试具有异步功能的 React 组件
【发布时间】:2020-02-25 10:26:58
【问题描述】:

我有一个组件:

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;

如果我直接从这个组件的实例调用方法,我可以成功地测试状态正在更新。但是,如果我模拟按钮单击,则不会更新任何内容并且测试失败。我已经尝试过 setImmediatesetTimeout 技巧,但这些都不起作用。

到目前为止,我还不能为以下内容编写测试用例:

  • 模拟按钮点击。
  • 模拟生命周期方法。

这是我迄今为止想出的。

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;
};

请帮我找出一个名为“前端测试”的谜题中缺少的部分。

【问题讨论】:

    标签: reactjs jestjs enzyme


    【解决方案1】:
    1. 我们需要mock 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: ...} }))
    
    1. 由于模拟是一个 Promise(已解决 - 但仍然是 Promise),我们需要 setTimeoutawait &lt;anything&gt; 以使组件的代码实现此 Promise 已解决。这都是关于microtasks/macrotasks queue
    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;

    1. 引用state和直接调用实例方法都是反模式。只是引用(Kent C. Dodds 我完全同意):

    总而言之,如果您的测试使用 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
      });
    });
    
    

    【讨论】:

    • 嗨@skyboyer 感谢您提供如此详细的解释。我知道对实例进行测试是毫无意义的。我只是通过一个关于玩笑和酶的随机教程。无论如何,我已经更新了描述并添加了fetchRandom 方法的定义。我不能打电话给mockReturnValue
    • 抱歉,我不明白这种情况下的“我不能打电话”。如上所示,您添加了jest.mock( "../resources/api"); 吗?如果你这样做了,fetchRandom 肯定应该有mockReturnValue 方法
    猜你喜欢
    • 2020-09-21
    • 2019-04-24
    • 2020-09-14
    • 2019-10-30
    • 2020-07-15
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 2019-07-09
    相关资源
    最近更新 更多