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

升级jsdom至21.0.0+后无法SpyOn window.location的解决方案咨询

解决jsdom 21+中window.location不可配置导致的Jest测试失败问题

我们计划将jsdom版本升级至26.0.0,但自jsdom 21.0.0起,window.location被设为不可配置项,导致原本依赖jest.spyOn(window, 'location')的测试报错:

Property `location` is not declared configurable

相关业务代码、测试代码及Jest配置如下:

export const getSearchParam = (name: string): string | boolean | undefined => {
  const params = new URLSearchParams(window.location.search);

  const value = params.get(name)?.trim();
  if (!value) {
    return undefined;
  }

  const lowerValue = value.toLowerCase();
  if (lowerValue === 'true') {
    return true;
  }
  if (lowerValue === 'false') {
    return false;
  }

  return value;
};

describe('getSearchParam', () => {
  beforeEach(() => {
    jest.spyOn(window, 'location', 'get').mockReturnValue({ search: '?testParam=value123&flag=true' } as Location);
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should return the correct search parameter value', () => {
    expect(getSearchParam('testParam')).toBe('value123');
  });
});

package.json中的Jest配置:

"jest": {
  "preset": "jest-preset-angular",
  "testEnvironment": "jest-preset-angular/environments/jest-jsdom-env",
  "setupFilesAfterEnv": [
    "<rootDir>/src/setup.jest.ts"
  ],
  "testPathIgnorePatterns": [
    "<rootDir>/node_modules/",
    "<rootDir>/dist/",
    "e2e-tests"
  ],
  "reporters": [
    "default"
  ],
  "clearMocks": true
}

可行的修改方案

方法1:重构业务代码,注入依赖(推荐)

将window.location作为可选参数传入函数,测试时直接传入模拟对象,完全避免修改全局对象:

修改后的业务代码:

export const getSearchParam = (name: string, location: Pick<Location, 'search'> = window.location): string | boolean | undefined => {
  const params = new URLSearchParams(location.search);

  const value = params.get(name)?.trim();
  if (!value) {
    return undefined;
  }

  const lowerValue = value.toLowerCase();
  if (lowerValue === 'true') {
    return true;
  }
  if (lowerValue === 'false') {
    return false;
  }

  return value;
};

对应的测试代码:

describe('getSearchParam', () => {
  it('should return the correct search parameter value', () => {
    expect(getSearchParam('testParam', { search: '?testParam=value123&flag=true' })).toBe('value123');
  });

  it('should return true for "true" flag', () => {
    expect(getSearchParam('flag', { search: '?flag=true' })).toBe(true);
  });

  it('should return undefined for non-existent param', () => {
    expect(getSearchParam('nonexist', { search: '?testParam=value123' })).toBeUndefined();
  });
});

这种方式降低了代码耦合度,测试更灵活,也不会产生全局副作用。

方法2:临时修改location.search属性

如果不想改动业务代码,可以通过Object.defineProperty临时修改location.search的可写性来模拟:

修改后的测试代码:

describe('getSearchParam', () => {
  let originalSearch: string;

  beforeEach(() => {
    originalSearch = window.location.search;
    // 临时设置search为可写并赋值
    Object.defineProperty(window.location, 'search', {
      writable: true,
      value: '?testParam=value123&flag=true'
    });
  });

  afterEach(() => {
    // 恢复原始值和属性配置
    Object.defineProperty(window.location, 'search', {
      writable: true,
      value: originalSearch
    });
  });

  it('should return the correct search parameter value', () => {
    expect(getSearchParam('testParam')).toBe('value123');
  });
});

该方法依赖当前jsdom对location.search的实现,后续版本可能有变动,但26.0.0版本可正常使用。

方法3:使用jsdom的reconfigure API修改全局location

通过jsdom内置的reconfigure方法直接修改整个location对象:

修改后的测试代码:

describe('getSearchParam', () => {
  let originalUrl: string;

  beforeEach(() => {
    originalUrl = window.location.href;
    // @ts-ignore 忽略TypeScript类型检查
    window.location.reconfigure('http://example.com/?testParam=value123&flag=true');
  });

  afterEach(() => {
    // @ts-ignore
    window.location.reconfigure(originalUrl);
  });

  it('should return the correct search parameter value', () => {
    expect(getSearchParam('testParam')).toBe('value123');
  });
});

这种方式更贴近真实浏览器行为,但需要忽略TypeScript类型检查,或补充jsdom相关类型声明。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:12:02