我该如何测试用Typescript进行api调用的React Hook“ useEffect”?

Mat*_*erg 12 typescript jestjs enzyme axios react-hooks

我正在使用Typescript和新的React钩子为一个简单的React应用编写一些有趣的酶测试。

但是,我似乎无法正确模拟在useEffect挂钩内进行的api调用。

useEffect进行api调用,并useState使用“ setData” 更新状态“ data”。

然后将对象“数据”映射到表中与其对应的表单元格中。

似乎应该可以通过模拟的api响应和酶安装来轻松解决,但是我不断收到错误消息,告诉我act()用于组件更新。

我尝试了act()许多方法,但无济于事。我试过用axiosfetch 代替,并使用酶浅和react-test-library的渲染,但是似乎没有任何效果。

组件:

import axios from 'axios'
import React, { useEffect, useState } from 'react';

interface ISUB {
  id: number;
  mediaType: {
    digital: boolean;
    print: boolean;
  };
  monthlyPayment: {
    digital: boolean;
    print: boolean;
  };
  singleIssue: {
    digital: boolean;
    print: boolean;
  };
  subscription: {
    digital: boolean;
    print: boolean;
  };
  title: string;
}

interface IDATA extends Array<ISUB> {}

const initData: IDATA = [];

const SalesPlanTable = () => {
  const [data, setData] = useState(initData);
  useEffect(() => {
    axios
      .get(`/path/to/api`)
      .then(res => {
        setData(res.data.results);
      })
      .catch(error => console.log(error));
  }, []);

  const renderTableRows = () => {
    return data.map((i: ISUB, k: number) => (
      <tr key={k}>
        <td>{i.id}</td>
        <td>
          {i.title}
        </td>
        <td>
          {i.subscription.print}
          {i.mediaType.digital}
        </td>
        <td>
          {i.monthlyPayment.print}
          {i.monthlyPayment.digital}
        </td>
        <td>
          {i.singleIssue.print}
          {i.singleIssue.digital}
        </td>
        <td>
          <button>Submit</button>
        </td>
      </tr>
    ));
  };

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>MediaType</th>
          <th>MonthlyPayment</th>
          <th>SingleIssue</th>
          <th/>
        </tr>
      </thead>
      <tbody'>{renderTableRows()}</tbody>
    </table>
  );
};

export default SalesPlanTable;

Run Code Online (Sandbox Code Playgroud)

考试:

const response = {
  data: {
    results: [
      {
        id: 249,
        mediaType: {
          digital: true,
          print: true
        },
        monthlyPayment: {
          digital: true,
          print: true
        },
        singleIssue: {
          digital: true,
          print: true
        },
        subscription: {
          digital: true,
          print: true
        },
        title: 'ELLE'
      }
    ]
  }
};

//after describe

it('should render a proper table data', () => {
    const mock = new MockAdapter(axios);
    mock.onGet('/path/to/api').reply(200, response.data);
    act(() => {
      component = mount(<SalesPlanTable />);
    })
    console.log(component.debug())
  });
Run Code Online (Sandbox Code Playgroud)

我希望它记录与呈现的表体部分表的HTML,我尝试了一些异步和不同的方式来嘲笑axios,但我保持要么想起来的表头或消息:更新SalesPlanTable内部测试没有被包裹在act(...).我花了好几个小时寻找解决方案,但找不到任何可行的方法,因此我决定鼓起勇气,在这里问一下。

Bri*_*ams 12

这里有两个问题


异步调用 setData

setDataPromise回调中被调用。

只要一Promise做出决议,任何回调等待它在得到排队PromiseJobs队列。PromiseJobs队列中的所有挂起作业都在当前消息完成之后且下一个消息开始之前运行。

在这种情况下,当前正在运行的消息是你的测试,以便在测试完成之前Promise回调有机会运行,setData不叫,直到您的测试完成。

您可以通过使用类似于setImmediate延迟断言的方式来解决此问题,直到PromiseJobs中的回调有机会运行之后。

看起来您还需要调用component.update()以使用新状态重新渲染组件。(我猜这是因为状态更改发生在之外,act因为没有任何方法可以将回调代码包装在中act。)

总之,工作测试如下所示:

it('should render a proper table data', done => {
  const mock = new MockAdapter(axios);
  mock.onGet('/path/to/api').reply(200, response.data);
  const component = mount(<SalesPlanTable />);
  setImmediate(() => {
    component.update();
    console.log(component.debug());
    done();
  });
});
Run Code Online (Sandbox Code Playgroud)

警告:...的更新未包含在act(...)中

该警告是由发生在之外的组件状态更新触发的act

函数触发异步调用setDatauseEffect引起的状态改变总是发生在act

这是一个非常简单的测试,演示了此行为:

import React, { useState, useEffect } from 'react';
import { mount } from 'enzyme';

const SimpleComponent = () => {
  const [data, setData] = useState('initial');

  useEffect(() => {
    setImmediate(() => setData('updated'));
  }, []);

  return (<div>{data}</div>);
};

test('SimpleComponent', done => {
  const wrapper = mount(<SimpleComponent/>);
  setImmediate(done);
});
Run Code Online (Sandbox Code Playgroud)

当我在寻找更多信息时,我偶然发现enzyme问题#2073在10个小时前就开始讨论这种行为。

在评论中添加了上述测试以帮助enzyme开发人员解决此问题。

  • 测试通过,但仍然得到“警告:测试内Post的更新未包含在act(...)中。” (2认同)
  • 我能够通过将 setImmediate 包装在“act”中来删除警告,如下所示:``await act(() =&gt; new Promise&lt;void&gt;((resolve) =&gt; { setImmediate(() =&gt; { node.更新();解决();});}))``` (2认同)

jpe*_*nna 5

解决方案

它既有效消除了警告test was not wrapped in act(...)

const waitForComponentToPaint = async (wrapper) => {
   await act(async () => {
     await new Promise(resolve => setTimeout(resolve, 0));
     wrapper.update();
   });
};
Run Code Online (Sandbox Code Playgroud)

用法:

it('should do something', () => {
    const wrapper  = mount(<MyComponent ... />);
    await waitForComponentToPaint(wrapper);
    expect(wrapper).toBlah...
})
Run Code Online (Sandbox Code Playgroud)

谢谢...

这是edpark11在他的回答中提到的问题@Brian_Adams中建议的解决方法。

原帖:https://github.com/enzymejs/enzyme/issues/2073#issuecomment-565736674

为了存档,我将帖子复制到此处并进行了一些修改。