如何使用TypeScript与Jest Mock模块中被调用的函数
正确Mock Jest中const导出函数的方法
要测试的函数(action目录下)
import { Action, ActionProgress, ActionStatus, MagicLinkProgress } from '../../interfaces' import { areSameActions } from '../actionsProgress' export const findActionProgressToDisplay = (progress: MagicLinkProgress, actions: Array<Action>): ActionProgress => { const actionProgressFound = progress.actionsProgress.find( (actionProgress) => actionProgress.status === ActionStatus.PENDING && actions.some((action) => areSameActions(actionProgress.action, action)) ) if (!actionProgressFound) { throw new Error('No action found to display') } return actionProgressFound }
该函数findActionProgressToDisplay依赖从../actionsProgress导入的areSameActions函数。
待Mock的函数
import { Action } from '../../interfaces' export const areSameActions = (firstAction: Action, secondAction: Action): boolean => firstAction.origin === secondAction.origin && firstAction.type === secondAction.type && firstAction.url === secondAction.url
三次失败的尝试及报错
第一种尝试
import { mockActions } from './../../mocks/campaigns/index' import { mockActionsProgress, mockProgress } from './../../mocks/magicLinks/index' import { findActionProgressToDisplay } from './index' import { describe, test, expect } from '@jest/globals' import { ActionStatus } from '../../interfaces' import * as module from '../actionsProgress/index' module.areSameActions = jest.fn().mockReturnValue(true) describe('=== findActionProgressToDisplay ===', () => { test('should return true', () => { const progress = mockProgress({ actionsProgress: mockActionsProgress() }) const actions = mockActions() const actionFound = findActionProgressToDisplay(progress, actions) expect(actionFound.status).toEqual(ActionStatus.PENDING) }) })
报错信息:Cannot assign to 'areSameActions' because it is a read-only property(符合预期,因为areSameActions是const导出的常量)
第二种尝试
import { mockActions } from './../../mocks/campaigns/index' import { mockActionsProgress, mockProgress } from './../../mocks/magicLinks/index' import { findActionProgressToDisplay } from './index' import { describe, test, expect } from '@jest/globals' import { ActionStatus } from '../../interfaces' import * as module from '../actionsProgress/index' jest.spyOn(module, 'areSameActions').mockReturnValue(true) describe('=== findActionProgressToDisplay ===', () => { test('should return true', () => { const progress = mockProgress({ actionsProgress: mockActionsProgress() }) const actions = mockActions() const actionFound = findActionProgressToDisplay(progress, actions) expect(actionFound.status).toEqual(ActionStatus.PENDING) }) })
报错信息:TypeError: Cannot redefine property: areSameActions
第三种尝试
import { mockActions } from './../../mocks/campaigns/index' import { mockActionsProgress, mockProgress } from './../../mocks/magicLinks/index' import { findActionProgressToDisplay } from './index' import { describe, test, expect } from '@jest/globals' import { ActionStatus } from '../../interfaces' import { areSameActions } from '../actionsProgress/index' jest.mock('../actionsProgress/index', () => { const original = jest.requireActual('../actionsProgress/index') // Step 2. return { ...original, areSameActions: jest.fn(), } }) describe('=== findActionProgressToDisplay ===', () => { test('should return true', () => { areSameActions.mockImplementation(() => (true)) const progress = mockProgress({ actionsProgress: mockActionsProgress() }) const actions = mockActions() const actionFound = findActionProgressToDisplay(progress, actions) expect(actionFound.status).toEqual(ActionStatus.PENDING) }) })
报错信息:Property 'mockImplementation' does not exist on type '(firstAction: Action, secondAction: Action) => boolean'(TypeScript类型推断认为areSameActions是普通函数,而非Mock函数)
正确的解决方案
针对TypeScript环境下Mock const导出函数的问题,推荐以下两种可靠方式:
方式一:类型断言处理Mock函数
修改第三种尝试的导入和Mock调用逻辑,通过类型断言明确告诉TypeScriptareSameActions是Jest Mock函数:
import { mockActions } from './../../mocks/campaigns/index' import { mockActionsProgress, mockProgress } from './../../mocks/magicLinks/index' import { findActionProgressToDisplay } from './index' import { describe, test, expect, jest } from '@jest/globals' import { ActionStatus } from '../../interfaces' import * as actionsProgressModule from '../actionsProgress/index' // 用jest.mock替换原模块,保留其他导出的同时替换目标函数为Mock jest.mock('../actionsProgress/index', () => { const original = jest.requireActual('../actionsProgress/index') return { ...original, areSameActions: jest.fn(), } }) describe('=== findActionProgressToDisplay ===', () => { test('should return the pending action progress', () => { // 通过类型断言将普通函数类型转为Jest Mock类型 const mockAreSameActions = actionsProgressModule.areSameActions as jest.Mock mockAreSameActions.mockReturnValue(true) const progress = mockProgress({ actionsProgress: mockActionsProgress() }) const actions = mockActions() const actionFound = findActionProgressToDisplay(progress, actions) expect(actionFound.status).toEqual(ActionStatus.PENDING) // 额外验证Mock函数是否被调用 expect(mockAreSameActions).toHaveBeenCalled() }) })
方式二:spyOn开启可写选项(可选)
如果不想使用jest.mock,可以在jest.spyOn中添加{ writable: true }选项,允许重写const导出的函数(注意:严格模式下可能受限):
import * as actionsProgressModule from '../actionsProgress/index' // 开启writable选项允许修改只读属性 jest.spyOn(actionsProgressModule, 'areSameActions', { writable: true }).mockReturnValue(true)
此方式更适合快速临时Mock,长期来看第一种方式的类型安全性和可维护性更好。
内容的提问来源于stack exchange,提问作者Damien
相关产品推荐
相关产品推荐

