我有以下(简化)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
-
mock SalesService
-
设置mockSalesService以返回在解析时返回预定义测试数据的promise
-
创建组件
-
等待
-
检查快照
测试SalesChart的外观不是这个问题的一部分,我希望使用Enzyme来解决这个问题 . 我一直在尝试几十种来模拟这个异步调用,但我似乎无法正确地嘲笑它 . 我在网上找到了以下Jest模拟的例子,但它们似乎没有涵盖这个基本用法 .
-
Hackernoon:不使用异步调用
-
Wehkamp tech blog:不使用异步调用
-
Agatha Krzywda:不使用异步调用
-
GitConnected:不使用具有模拟功能的类
-
Jest tutorial An Async Example:不使用具有函数的类进行模拟
-
Jest tutorial Testing Asynchronous Code:不使用具有模拟功能的类
-
SO question 43749845:我无法以这种方式将模拟连接到实际实现
-
42638889:使用依赖注入,我不是
-
46718663:未显示实际模拟类的实现方式
我的问题是:
-
模拟类应该怎么样?
-
我应该把这个模拟课放在哪里?
-
我该如何导入这个模拟类?
-
如何判断这个模拟类取代了真正的类?
-
如何设置mock类的特定函数的模拟实现?
-
如何在测试用例中等待承诺得到解决?
我有一个不起作用的例子如下 . 测试运行器因错误 throw err;
而崩溃,堆栈跟踪中的最后一行是 at process._tickCallback (internal/process/next_tick.js:188:7)
# __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();
});
3 回答
有时,当测试很难写时,它试图告诉我们我们有设计问题 .
我认为一个小型的重构可以让事情变得更容易 - 让
SalesService
成为合作者而不是内部合作者 .我的意思是,不是在组件中调用
new SalesService()
,而是通过调用代码接受销售服务作为prop . 如果你这样做,那么调用代码也可以是你的测试,在这种情况下,你需要做的就是模拟SalesService
本身,并返回你想要的任何东西(使用sinon或任何其他模拟库,甚至只是创建一个手动滚动存根) .您可以使用
SalesService.create()
方法抽象new
关键字,然后使用jest.spyOn(object, methodName)来模拟实现 .我过去使用的一种“丑陋”方式是做一种穷人的依赖注入 .
它基于这样一个事实:您可能并不真的想在每次需要时实例化
SalesService
,而是希望每个应用程序保留一个实例,每个人都使用它 . 在我的情况下,SalesService
需要一些初始配置,我不想每次都重复 . [1]所以我所做的是有一个
services.ts
文件,如下所示:然后,在我的应用程序的
index.tsx
或我有的类似地方:在组件中,您可以使用
getSalesService
获取每个应用程序的一个SalesService
实例的引用 .当需要测试时,您只需要在
mocha
(或其他)before
或beforeEach
处理程序中进行一些设置,以使用模拟对象调用setSalesService
.现在,理想情况下,你想要将
SalesService
作为道具传递到你的组件,因为它是它的输入,并且通过使用getSalesService
你使用路由器或某些东西,将它作为道具传递变得非常笨拙 .您也可以使用类似context之类的东西,将所有内容保存在React中 .
这种“理想”的解决方案就像依赖注入一样,但这不是React AFAIK的选择 .
[1]它还可以帮助提供一个单点来序列化远程服务调用,这可能在某些时候需要 .