You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest重复Mock navigator测试userAgent/vendor时的缓存问题排查

循环Mock navigator.userAgent时后续测试读取旧值的问题

测试意图

  • 循环使用不同的userAgent组合开展测试
  • Mock navigator对象模拟不同设备环境
  • 验证目标函数输出是否符合预期

当前问题

  • 首次Mock navigator.userAgent成功,测试用例执行结果符合预期
  • 第二次执行Mock后,测试仍读取第一次Mock的userAgent值,导致结果异常

待测试函数

export const isBrowseriOS = () => {
  console.log(navigator.userAgent, ' & ', navigator.vendor);
  return (
    navigator.vendor === 'Apple Computer, Inc.' ||
    (/iPad|iPhone|iPod/.test(navigator.userAgent) &&
      !window.MSStream &&
      'ontouchend' in document)
  );
};

测试文件代码

import { isBrowseriOS } from './isIOS';

const browserDevices = [
  {
    name: 'iPhone 12 Pro',
    userAgent:
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
    vendor: 'Apple Computer, Inc.',
    iOSDevice: true,
  },
   {
     name: 'Samsung SM-G955U',
     userAgent:
       'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',
     vendor: 'Google Inc.',
     iOSDevice: false,
   },
  
];

describe.each(browserDevices)('iOS test for $name', (device) => {
  beforeEach(() => {
    Object.defineProperty(window, 'navigator', {
      value: {
        userAgent: device.userAgent,
        vendor: device.vendor,
      },
    });
  });
  afterEach(() => jest.resetAllMocks());
  it(`returns ${device.iOSDevice}`, () => {
    expect(isBrowseriOS()).toBe(device.iOSDevice);
  });
});

测试结果

测试结果异常,第二个测试用例未读取到正确的Mock值,仍然沿用第一次的userAgent数据。

解决方案

问题根源在于Object.defineProperty默认配置下,window.navigator属性不可修改,首次定义后后续无法重新赋值。需要添加configurable: true允许属性被重新定义,同时在测试后恢复原始navigator对象,避免测试环境污染。

修改后的测试代码如下:

import { isBrowseriOS } from './isIOS';

const browserDevices = [
  {
    name: 'iPhone 12 Pro',
    userAgent:
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
    vendor: 'Apple Computer, Inc.',
    iOSDevice: true,
  },
   {
     name: 'Samsung SM-G955U',
     userAgent:
       'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',
     vendor: 'Google Inc.',
     iOSDevice: false,
   },
  
];

// 保存原始navigator对象,用于测试后恢复
const originalNavigator = window.navigator;

describe.each(browserDevices)('iOS test for $name', (device) => {
  beforeEach(() => {
    Object.defineProperty(window, 'navigator', {
      value: {
        userAgent: device.userAgent,
        vendor: device.vendor,
      },
      configurable: true, // 允许重新定义该属性
    });
  });
  afterEach(() => {
    // 恢复原始navigator,清除测试环境修改
    delete window.navigator;
    Object.defineProperty(window, 'navigator', {
      value: originalNavigator,
      configurable: true,
    });
  });
  it(`returns ${device.iOSDevice}`, () => {
    expect(isBrowseriOS()).toBe(device.iOSDevice);
  });
});

关键说明

  1. configurable: true:解除属性不可修改的限制,让每次测试都能重新定义navigator
  2. 保存并恢复原始navigator:确保测试完成后环境回到初始状态,避免影响其他测试用例
  3. 替换jest.resetAllMocks():该方法无法重置通过Object.defineProperty添加的属性,必须手动恢复环境

内容的提问来源于stack exchange,提问作者Akber Iqbal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 01:06:20