单元测试中Mock函数的决策依据:以数字格式化函数测试场景为例的疑问
你给出的数字格式化函数和对应的测试代码如下:
export const numberFormat = (num) => { return new Intl.NumberFormat('en-EN', { style: 'decimal', maximumFractionDigits: 2 }).format(num) }import { test, expect } from 'vitest' import { numberFormat } from './numberFormat.js' const numValue = 1000.12345678 test('number is formatted', () => { expect(numberFormat(numValue)).toBe('1,000.12') })
你的这个测试写法完全没问题,甚至可以说是非常精准合理的选择,完全没必要去mock这个numberFormat函数!
咱们先拆解下你的场景:你写的numberFormat是个标准的纯函数——输入确定的数字,输出唯一确定的格式化字符串,而且它依赖的Intl.NumberFormat是浏览器/JS运行环境原生提供的稳定API,你还明确指定了en-EN的区域规则,几乎不存在不稳定的变量。
那什么时候需要考虑mock?核心记住一个原则:mock的唯一目的是隔离干扰,让你能聚焦在当前要测试的核心逻辑上,举两个典型场景:
- 假设你有另一个业务函数
displayProductPrice,它的逻辑是获取商品原始价格,调用numberFormat格式化,再拼接货币符号。这时候你测试displayProductPrice时,就可以mocknumberFormat——因为你要测的是displayProductPrice的业务逻辑(比如有没有正确传参、有没有加对货币符号),而不是再重复测一次数字格式化的规则。 - 如果你的
numberFormat不是依赖原生API,而是调用了某个第三方远程格式化服务,那必须mock这个远程请求——不然每次测试都要发真实请求,既慢又不稳定,还依赖网络。
但回到你的测试本身:你就是要验证「我写的这个格式化函数,能不能按照预期把数字转成指定格式的字符串」。这时候如果去mocknumberFormat,那你测试的就不是这个函数的真实功能了——难道你要mock它返回固定的'1,000.12'?那你的测试就变成了“我让mock返回什么,测试就断言什么”,完全失去了单元测试验证代码正确性的意义。
再直白点说:你现在的测试就像“我买了个新计算器,按1000.12345678,再按格式化键,看看是不是出1,000.12”——这才是正常的验证方式,总不能把计算器的格式化功能给“mock”成固定输出,那你怎么知道这个计算器的格式化功能真的好用?
总结一下:当你测试的目标是某个函数本身的正确性,且这个函数没有不稳定的外部依赖时,直接调用真实函数测试结果才是最优解;mock只适用于需要隔离外部依赖、聚焦非当前测试目标逻辑的场景。
备注:内容来源于stack exchange,提问作者gespinha

