如何在另一个类中模拟异步函数调用

问题描述 投票:21回答:3

我有以下(简化)React组件。

class SalesView extends Component<{}, State> {
  state: State = {
    salesData: null
  };

  componentDidMount() {
    this.fetchSalesData();
  }

  render() {
    if (this.state.salesData) {
      return <SalesChart salesData={this.state.salesData} />;
    } else {
      return <p>Loading</p>;
    }
  }

  async fetchSalesData() {
    let data = await new SalesService().fetchSalesData();
    this.setState({ salesData: data });
  }
}

安装时,我从API中获取数据,我在一个名为SalesService的类中抽象出来。这个类我想模拟,对于方法fetchSalesData我想指定返回数据(在一个promise中)。

这或多或少是我希望我的测试用例看起来像:

  • 预定义测试数据
  • 导入SalesView
  • 模拟SalesService
  • 设置mockSalesService以返回在解析时返回预定义测试数据的promise
  • 创建组件
  • 等待
  • 检查快照

测试SalesChart的外观不是这个问题的一部分,我希望使用Enzyme来解决这个问题。我一直在尝试几十种来模拟这个异步调用,但我似乎无法正确地嘲笑它。我在网上找到了以下Jest模拟的例子,但它们似乎没有涵盖这个基本用法。

我的问题是:

  • 模拟课应该怎么样?
  • 我应该把这个模拟课放在哪里?
  • 我该如何导入这个模拟类?
  • 我如何判断这个模拟类取代了真正的类?
  • 如何设置模拟类的特定函数的模拟实现?
  • 我如何在测试用例中等待承诺得到解决?

我有一个不起作用的例子如下。测试运行器崩溃时出现错误46718663,堆栈跟踪中的最后一行是throw err;

at process._tickCallback (internal/process/next_tick.js:188:7)
javascript reactjs unit-testing asynchronous jestjs
3个回答
12
投票

有时,当测试很难写时,它试图告诉我们我们有设计问题。

我认为一个小型重构可以让事情变得更容易 - 让# __tests__/SalesView-test.js import React from 'react'; import SalesView from '../SalesView'; jest.mock('../SalesService'); const salesServiceMock = require('../SalesService').default; const weekTestData = []; test('SalesView shows chart after SalesService returns data', async () => { salesServiceMock.fetchSalesData.mockImplementation(() => { console.log('Mock is called'); return new Promise((resolve) => { process.nextTick(() => resolve(weekTestData)); }); }); const wrapper = await shallow(<SalesView/>); expect(wrapper).toMatchSnapshot(); }); 成为合作者而不是内部合作者。

我的意思是,不是在组件中调用SalesService,而是通过调用代码接受销售服务作为prop。如果你这样做,那么调用代码也可以是你的测试,在这种情况下,你需要做的就是模拟new SalesService()本身,并返回你想要的任何东西(使用sinon或任何其他模拟库,甚至只是创建一个手动滚动存根)。


5
投票

您可以使用SalesService方法抽象new关键字,然后使用SalesService.create()来模拟实现。

jest.spyOn(object, methodName)

2
投票

我过去使用的一种“丑陋”方式是做一种穷人的依赖注入。

它基于这样一个事实,即你可能不想在每次需要时实例化import SalesService from '../SalesService '; test('SalesView shows chart after SalesService returns data', async () => { const mockSalesService = { fetchSalesData: jest.fn(() => { return new Promise((resolve) => { process.nextTick(() => resolve(weekTestData)); }); }) }; const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService); const wrapper = await shallow(<SalesView />); expect(wrapper).toMatchSnapshot(); expect(spy).toHaveBeenCalled(); expect(mockSalesService.fetchSalesData).toHaveBeenCalled(); spy.mockReset(); spy.mockRestore(); }); ,而是希望每个应用程序都拥有一个实例,每个人都使用它。在我的情况下,SalesService需要一些初始配置,我不想每次都重复。[1]

所以我做的是有一个SalesService文件,看起来像这样:

services.ts

然后,在我的应用程序的/// In services.ts let salesService: SalesService|null = null; export function setSalesService(s: SalesService) { salesService = s; } export function getSalesService() { if(salesService == null) throw new Error('Bad stuff'); return salesService; } 或我有一些类似的地方:

index.tsx

在组件中,您可以使用/// In index.tsx // initialize stuff const salesService = new SalesService(/* initialization parameters */) services.setSalesService(salesService); // other initialization, including calls to React.render etc. 获取每个应用程序的一个getSalesService实例的引用。

当需要测试时,你只需要在你的SalesService(或其他)mochabefore处理程序中进行一些设置,用模拟对象调用beforeEach

现在,理想情况下,你想要将setSalesService作为道具传递给你的组件,因为它是对它的输入,并且通过使用SalesService你隐藏了这种依赖性并可能导致你的悲伤。但是如果你在一个非常嵌套的组件中需要它,或者如果你正在使用路由器或其他东西,那么将它作为道具传递就变得非常笨拙了。

你可能也会使用像getSalesService这样的东西,以保持React内部的所有内容。

这种“理想”的解决方案就像依赖注入一样,但这不是React AFAIK的选择。


[1]它还可以帮助提供一个单点来序列化远程服务调用,这可能在某些时候需要。

© www.soinside.com 2019 - 2024. All rights reserved.