测试 React 应用程序时如何模拟获取?

How to mock fetch when testing a React app?

我想测试一个使用全局 fetch 方法的小型 React 网络应用程序。

我试过这样模拟fetch

global.fetch = jest.spyOn(global, 'fetch').mockImplementation(endpoint =>
  Promise.resolve({
    json: () => Promise.resolve(mockResponse)
  })
);

...但是 mock 似乎被忽略了,而内置的 fetch 似乎被使用了:Error: connect ECONNREFUSED 127.0.0.1:80 ... 看起来像是对内置 [=14= 的调用失败].

然后我尝试使用 jest.fn 而不是 jest.spyOn:

global.fetch = jest.fn(endpoint =>
  Promise.resolve({
    json: () => Promise.resolve(mockResponse)
  })
);

... 并惊讶地发现了一个不同的错误。现在mock好像也考虑进去了,但是同时也不能正常工作:

    TypeError: Cannot read property 'then' of undefined

       8 |     this.updateTypes = this.props.updateTypes;
       9 |     this.updateTimeline = this.props.updateTimeline;
    > 10 |     fetch('/timeline/tags')
         |     ^
      11 |       .then(res => res.json())
      12 |       .then(tags => tags.map(tag => <option value={tag} key={tag} />))
      13 |       .then(options => options.sort((a, b) => a.key.localeCompare(b.key)))

老实说,我发现 Jest 和 React 测试库的文档有点混乱。我正在做的事情可能有什么问题?

编辑

我正在尝试测试的 React 组件名为 "App",是使用 Create React App 生成的,并已更改为包含对 fetch 的调用。我很乐意提供此组件的代码,但我认为问题出在测试中。

在我的 App.test.js 文件的开头,我 import React from 'react';,然后是 import { render, fireEvent, waitFor, screen } from '@testing-library/react';,最后是 import App from './App';。我随后尝试以我描述的方式之一模拟 fetch,然后声明以下测试:

test('renders a list of items, upon request', async () => {
  const app = render(<App />);

  fireEvent.click(screen.getByText('Update'));

  await waitFor(() => screen.getByRole('list'));

  expect(screen.getByRole('list')).toBeInTheDocument();
  expect(screen.getByRole('list')).toHaveClass('Timeline');
});

最后,我以 global.fetch.mockRestore();.

结束我的测试文件

ECONNREFUSED 错误而不是 fetch is not defined 意味着 fetch 已被填充。它不是 JSDOM 的一部分,也不是由 Jest 本身填充的,而是特定于当前设置的。在这种情况下,polyfill 由 create-react-app 提供。

最好用 jest.spyOn 模拟现有的全局函数,而不是将它们分配为 global 属性,这允许 Jest 进行清理。永远不要做 global.fetch = jest.spyOn(global, 'fetch') 这样的事情,因为这会阻止 fetch 被恢复。这可以解释看似正确模拟函数的 TypeError: Cannot read property 'then' of undefined 错误。

模拟全局变量的正确且安全的方法是在每次测试前模拟它们并在每次测试后恢复:

beforeEach(() => {
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: jest.fn().mockResolvedValue(mockResponse)
  })
});

afterEach(() => {
  jest.restoreAllMocks();
});

不应对 global.fetch 进行其他修改以使模拟正常工作。

恢复模拟和间谍的一种更好的方法是使用 configuration 选项而不是 jest.restoreAllMocks,因为不这样做可能会导致意外的测试交叉污染,这是不可取的。

出现TypeError: Cannot read property 'then' of undefined错误的另一个原因是Jest错误地指向了fetch行,错误实际上指向了另一行。如果源映射不能正常工作,就会发生这种情况。如果 fetch 被正确模拟并且同一组件中还有其他 then,则这是对错误的合理解释。