Jest单元测试中mock navigator.language不生效问题
问题根因
两个核心问题导致用例失败:
- 逻辑分支未命中:你的
getLang函数优先判断navigator.languages,只要该值为真值就直接返回数组第一项,完全不会走到读取navigator.language的分支。Jest默认的jsdom测试环境自带navigator.languages属性,默认值为['en-US']。你在测试用例里直接打印navigator.language能看到mock值,是因为你手动读取了这个属性,但函数实际执行时走的是languages分支,直接返回默认的en-US,根本没访问navigator.language。 - mock方式适配问题:jsdom环境下navigator的部分原生属性getter定义在原型链上,直接用
jest.spyOn给当前navigator实例挂载getter,在部分场景下不会被模块内的代码正确读取到。
解决方案
方案1:补全分支mock(快速修复当前用例)
要测试navigator.language的返回逻辑,必须先把navigator.languages mock为假值,让代码走到目标分支,修改后的用例如下:
// utils.spec.ts it('should return exact response language if there is one', () => { // 先屏蔽languages分支,让代码走到读取language的逻辑 jest.spyOn(navigator, 'languages', 'get').mockReturnValueOnce(undefined); // 再mock目标language值 jest.spyOn(navigator, 'language', 'get').mockReturnValueOnce('en-fr'); expect(getLang()).toBe('en-fr'); });
修改后用例可直接通过。
方案2:全局属性描述符mock(通用稳定方案)
如果jest.spyOn在你的环境中仍有生效问题,可以直接通过Object.defineProperty修改全局navigator的属性定义,记得在用例执行后恢复原值,避免污染其他测试用例:
describe('Navigator', () => { // 缓存原始属性值,测试后恢复 const originalLanguages = navigator.languages; const originalLanguage = navigator.language; afterEach(() => { // 恢复原生属性 Object.defineProperty(navigator, 'languages', { get: () => originalLanguages, configurable: true }); Object.defineProperty(navigator, 'language', { get: () => originalLanguage, configurable: true }); jest.restoreAllMocks(); }); it('should return exact response language if there is one', () => { // 屏蔽languages分支 Object.defineProperty(navigator, 'languages', { get: () => undefined, configurable: true }); // 设置测试用的language值 Object.defineProperty(navigator, 'language', { get: () => 'en-fr', configurable: true }); expect(getLang()).toBe('en-fr'); }); it('should return 1st language if there array of languages', () => { Object.defineProperty(navigator, 'languages', { get: () => ['en-fr', 'en-US', 'en'], configurable: true }); expect(getLang()).toBe('en-fr'); }); });
避坑提示
- mock属性前先梳理代码分支优先级,不要只mock目标属性就断言结果,先确认代码确实会走到读取该属性的逻辑。
- 通过
Object.defineProperty修改全局属性时必须加configurable: true,否则后续无法重新修改属性,会导致用例之间状态污染。 - Jest 28及以上版本可使用官方提供的
jest.replaceProperty方法替换对象属性,比jest.spyOn更适合全局属性类的mock场景。
内容的提问来源于stack exchange,提问作者rufatZZ
相关产品推荐
相关产品推荐

