javascript - 使用异步方法测试 React 组件

标签 javascript reactjs promise jestjs

我有一个组件,其行为如下。

  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 测试是一场斗争。使用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 的 promise 或 setState 的异步性质(我不确定是哪一个)直到我的 expect 之后才会运行;文本保留为“正在加载”。

这是测试这样的代码的明智方法吗?您会以完全不同的方式编写这段代码吗?

此后我的问题升级了。我正在使用 high order component ,这意味着我无法再执行 .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`.

最佳答案

我必须承认以前没有真正使用过 jest-fetch-mock,但从文档和我的小实验来看,它看起来像是用模拟版本替换了全局 fetch 。请注意在此示例中如何不等待任何 promise :https://github.com/jefflau/jest-fetch-mock#simple-mock-and-assert 。它只是检查是否使用正确的参数调用了 fetch 。因此,我认为您可以删除 async/await 并断言存在对 jsonip.com 的调用。

我认为让你困惑的实际上是 React 生命周期。本质上,它归结为您放置 fetch 调用的位置。 React 团队不鼓励您在构造函数中添加“副作用”(例如fetch)。这是官方 React 文档描述:https://reactjs.org/docs/react-component.html#constructor 。不幸的是,我还没有真正找到关于原因的良好文档。我相信这是因为 React 可能会在生命周期中的奇怪时间调用构造函数。我认为这也是您必须在测试中手动调用 fetchData 函数的原因。

放置副作用的最佳实践位于 componentDidMount 。这是原因的一个很好的解释:https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/ (尽管值得注意的是,componentWillMount 现在在 React 16.2 中已被弃用)。 componentDidMount 仅在组件渲染到 DOM 后调用一次。

还值得注意的是,随着即将推出的 React 版本,这一切都将很快改变。这篇博文/ session 视频介绍了更多细节:https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

这种方法意味着它将最初在加载状态下渲染,但是一旦请求解决,您可以通过设置状态触发重新渲染。因为您在测试中使用 Enzyme 中的 mount,所以这将调用所有必要的生命周期方法,包括 componentDidMount,因此您应该看到模拟的 fetch 被调用。

对于高阶组件,我有时会使用一个技巧,这可能不是最佳实践,但我认为这是一个非常有用的技巧。 ES6 模块有一个default 导出,以及您喜欢的许多“常规”导出。我利用它多次导出组件。

React 约定是在导入组件时使用 default 导出(即 import MyComponent from './my-component')。这意味着您仍然可以从文件中导出其他内容。

我的技巧是导出默认 HOC 包装的组件,以便您可以像使用任何其他组件一样在源文件中使用它,但导出展开的组件作为“常规”组件。这看起来像:

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

export default myHOCFunction()(MyComponent)

然后您可以使用以下方式导入包装的组件:

import MyComponent from './my-component'

以及未包装的组件(即用于测试的组件):

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

这不是世界上最明确的模式,但在我看来,它非常符合人体工程学。如果你想要明确,你可以这样做:

export const WrappedMyComponent = myHOCFunction()(MyComponent)
export const UnwrappedMyComponent = MyComponent

关于javascript - 使用异步方法测试 React 组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50320038/

相关文章:

javascript - 是否可以判断用户是否将我的页面最小化或处于非事件状态?

javascript - 如何修复 TypeError : browser. storage is undefined in chrome/firefox web extension?

javascript - 使用 Javascript 从 HTML 表单中获取值,然后将其显示在 html 上

javascript - 如何在 react 中获取对象中的嵌套数组?

javascript - NodeJS : promise returned by Promise. 尽管各个 promise 都已解决,但一切都没有解决

javascript - 在 Node.js 中调用一组 http.get 请求完成

javascript - 使用属性时如何调用函数?

node.js - 基于工作角色的授权在 react 中有多安全?

javascript - React警告错误停止编译应用程序

javascript - 用 Mocha 测试 promise