【问题标题】:Testing a React component with Jest / Enzyme & Axios使用 Jest / Enzyme & Axios 测试 React 组件
【发布时间】:2018-08-20 21:15:30
【问题描述】:

我有一个使用 Axios 获取数据的 react 组件:

import React from "react";
import axios from "axios";

export default class Example extends React.Component {
    constructor() {
        super();
        axios("http://localhost/data.json").then(response => {
            this.setState({
                data: response.data
            });
        });
    }

    render() {
        return <ul>{this.data.map(item => <li>{item}</li>)}</ul>;
    }
}

我正在尝试围绕这个组件编写一些基本测试。首先我只是想让组件渲染......

import React from "react";
import { shallow } from "enzyme";

import Example from "./example";

it.only("Renders", () => {
    const wrapper = shallow(<Example />);
    const li = wrapper.find("li");
    expect(li.length).toBe(2); // there are two items in the data file
});

...但是我得到的第一件事是来自 axios 的网络错误。

我是测试新手,我知道我需要进行模拟等组件的测试,但我的印象是你仍然可以使用实时端点,尽管它不是最佳的。

有没有办法告诉 Jest 等到组件渲染完成后再进行断言?

任何建议将不胜感激!

【问题讨论】:

  • 终于找到了我从 Axios 收到网络错误的原因 - 需要在 create-react-app 测试脚本中进行此设置才能将环境设置为节点..."test": "react-scripts test --env=jsdom --testEnvironment=node"This is mentioned here

标签: javascript reactjs axios jestjs enzyme


【解决方案1】:

我使用间谍 (sinonjs) 通过异步调用测试代码。间谍的想法是“监视”特定函数(即您的回调),并断言是否被调用和/或调用了多少次等。

测试间谍是一个函数,它记录所有调用的参数、返回值、this 的值和抛出的异常(如果有的话)。

let wrapper = shallow(<ReactComponent/>);
let handleEventSpy = sinon.spy(ReactComponentFile, 'functionName');

it('Event Handled', () => {
  const event = {... event stuffs ...};
  expect(handleEventSpy.called).tobe(false);
  wrapper.simulate('Event', event);
  expect(handleEventSpy.called).tobe(true)
}

您还可以使用Mocha 测试异步函数,其想法是包装称为异步函数的代码,向其传递一个特殊函数,测试可以在异步函数完成后调用该函数。 Mocha 也可以很好地与 Promise 配合使用,并且语法更加清晰。

我会推荐:

  1. 将您的响应处理程序放入它自己的函数中
    • 允许使用特定测试数据独立于 axios 进行测试
    • 根据数据更新的频率,性能会有所提高,而不是在每次调用时都绑定新函数
  2. 将您的列表渲染器放入它自己的函数中
    • 同上
    • 结合生命周期钩子,避免在 null 对象上调用 map 函数
  3. 使用React's component lifecycle hooks
  4. 虽然不是必需的,但最好将构造函数中的props 传递给父类
  5. 按照建议,模拟 axios 进行测试以避免长时间运行异步任务,并改用测试数据对象。
    • jest.mock('axios');

import React from "react";
import axios from "axios";

export default class Example extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    axios("http://localhost/data.json")
      .then(response => this.responseHandler)
      .catch(err => this.errorHandler);
  }

  responseHandler = ({data}) => this.setState({data});
  errorHandler = (err) => { ... handle error ...};
  renderRows = ({data}) = <>{data.map(item => this.renderRow}</>; // React Fragment, fyi
  renderRow = (item) => <li>{item}</li>;

  render() {
    return <ul>{this.renderRows(this.state)}</ul>;
  }
}

通过进一步分解组件,您可以编写更简单的测试,并且通过将处理程序与 axios 调用分开,您可以改用测试数据。您不是在尝试测试您的 API,对吧?

【讨论】:

  • 非常感谢,听起来这对于检查调用是否作为单独的测试很有用,但据我所知,我认为这不会有助于安装组件和运行测试输出。理想情况下,我想像我在问题中提出的粗略示例一样做出断言,但它并没有与数据一起增加!
  • 你读过 Mocha 的文档吗?我将您链接到测试异步代码部分。但更重要的是,我认为您应该更多地分解代码以使测试更容易。通常,如果您尝试在测试中呈现组件,则需要模拟您的异步 api 调用。 Jest 可以进行模拟,而 mocha 可以处理测试异步代码,但同时做这两者真的很难。您甚至试图在原始帖子中测试什么?
  • 很抱歉,在回复之前我没有在移动应用上看到您的答案的后半部分 - 回复太快,尽量不要粗鲁!您现在建议的重构对我来说很有意义。测试对我来说是新的,是的 - 编写易于单独测试的部分很有意义。你说我不打算测试 API 是正确的——我想这更像是一个集成测试,用“实时”数据渲染整个组件以断言整个输出。
  • 很棒的重构技巧。编写测试确实揭示了单一职责原则的价值。
【解决方案2】:

一般来说,您永远不想在 Javascript 的构造函数中放置异步调用。这是一种反模式,因为当构造函数返回时,您创建的对象处于不确定状态。

在 React 中,调用异步函数的初始化代码通常会进入到 componentDidMount 生命周期方法中。

如果你从 componentDidMount 返回一个 Promise,Enzyme 将在渲染期间等待 Promise 解决。

当然,您需要模拟 axios,以便在测试期间没有网络调用。您可以通过在测试文件中调用jest.mock('axios') 来做到这一点。

【讨论】:

  • 这些是非常有用的提示,谢谢。 “如果您从 componentDidMount 返回一个承诺,Enzyme 将在渲染期间等待该承诺解决。” - 就这篇文章而言,这正是我想要实现的目标 - 使用数据测试组件的渲染(无论这是否是一种合理的测试方法!)
  • @stone 谢谢,我还想评论 React 生命周期,所以我添加了它。
猜你喜欢
  • 2021-06-24
  • 2017-02-12
  • 2021-09-16
  • 2017-04-13
  • 1970-01-01
  • 2021-04-14
  • 2020-08-07
  • 2020-02-06
  • 1970-01-01
相关资源
最近更新 更多