Jest模拟fetch报TypeError:无法读取undefined的then属性
业务代码实现
编写的get方法通过fetch请求本地JSON文件并返回解析后数据,实现代码如下:
// eslint-disable-next-line import/prefer-default-export export const get = () => { // eslint-disable-next-line no-undef return fetch('data/posts.json').then((res) => res.json()); };
初始测试代码
Jest测试文件中编写的全局fetch mock与测试用例如下:
import { get } from './posts'; const mockData = [ { "id": "ig-1", "accountId": "IG", "accountIcon": "/images/ig-icon.svg", "accountName": "IG account", "accountImageInitial": "J", "imageUrl": "/images/social_logo.png", "caption": "test", "timestamp": 1635510651638 }, { "id": "fb-1", "accountId": "FB", "accountIcon": "/images/fb-icon.svg", "accountName": "FB account", "accountImageInitial": "J", "imageUrl": "/images/social_logo.png", "caption": "test", "timestamp": 1635510051638 } ]; global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve(mockData) }) ); describe('The posts API controller', () => { test('get() returns expected default payload', async () => { const result = await get(); expect(fetch).toHaveBeenCalledTimes(1); expect(result).toBeTruthy(); }); });
报错信息
运行测试时抛出如下错误:
TypeError: Cannot read properties of undefined (reading 'then')
对应的错误栈指向fetch调用行:
TypeError: Cannot read properties of undefined (reading 'then') 2 | export const get = () => { 3 | // eslint-disable-next-line no-undef > 4 | return fetch('data/posts.json').then((res) => res.json()); | ^ 5 | };
错误截图:
问题根因
报错核心原因是ES模块导入提升导致mock挂载时序错误:
ES模块规范下,所有import语句会被JS引擎提升到当前文件的最顶部优先执行,和代码书写位置无关。测试代码实际执行顺序为:
- 优先执行
import { get } from './posts'加载posts模块,此时Node环境默认无全局fetch,且mock赋值逻辑还未执行,模块内捕获到的全局fetch为undefined - 之后才执行
global.fetch = jest.fn(...)赋值逻辑,但此时posts模块已经拿到了undefined的fetch引用 - 测试用例调用
get()时,内部执行undefined.then(...)直接抛出类型错误
修复方案
使用Jest官方推荐的spyOn方式在测试初始化钩子里挂载mock,既解决时序问题,也方便测试后还原全局对象避免污染其他用例:
import { get } from './posts'; const mockData = [ { "id": "ig-1", "accountId": "IG", "accountIcon": "/images/ig-icon.svg", "accountName": "IG account", "accountImageInitial": "J", "imageUrl": "/images/social_logo.png", "caption": "test", "timestamp": 1635510651638 }, { "id": "fb-1", "accountId": "FB", "accountIcon": "/images/fb-icon.svg", "accountName": "FB account", "accountImageInitial": "J", "imageUrl": "/images/social_logo.png", "caption": "test", "timestamp": 1635510051638 } ]; describe('The posts API controller', () => { // 每个用例执行前挂载mock beforeEach(() => { jest.spyOn(global, 'fetch').mockResolvedValue({ json: jest.fn().mockResolvedValue(mockData) }); }); // 每个用例执行后还原所有mock,清理全局污染 afterEach(() => { jest.restoreAllMocks(); }); test('get() returns expected default payload', async () => { const result = await get(); expect(fetch).toHaveBeenCalledTimes(1); // 替换原有弱断言,直接校验返回值和mock数据完全一致 expect(result).toEqual(mockData); }); });
额外配置说明:如果使用Jest 28及以上版本,默认测试环境为node,本身不提供浏览器全局API,需要在jest配置文件中添加testEnvironment: "jsdom",模拟浏览器环境后再测试fetch相关逻辑。
内容的提问来源于stack exchange,提问作者Leon Gaban
相关产品推荐
相关产品推荐

