升级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
相关产品推荐
相关产品推荐

