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

IT技术 reactjs unit-testing jestjs fetch react-testing-library
2021-05-24 14:23:23

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

我试图以fetch这种方式嘲笑

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

...但模拟似乎被忽略,而内置fetch似乎被使用:Error: connect ECONNREFUSED 127.0.0.1:80 ...看起来像是对内置的调用失败fetch

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

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

...并惊讶地看到一个不同的错误。现在模拟似乎被考虑在内,但同时无法正常工作:

    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文件的开头, I 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();.

1个回答

ECONNREFUSED错误而不是fetch is not defined意味着fetch已被填充。它不是 JSDOM 的一部分,也不是 Jest 本身的 polyfill,而是特定于当前设置。在这种情况下,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为了使模拟正常工作,不应有其他修改

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

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