Angular 5中用Jest模拟window对象及修改location.href的最佳方法
我碰到过一模一样的问题!在Angular 5 + Jest的环境里测试页面重定向确实会踩这个坑——核心原因是Jest依赖的jsdom默认没有实现完整的页面导航API,直接修改window.location.href或者触发跳转就会抛出Not implemented: navigation (except hash changes)的错误。下面是我亲测有效的几种解决办法:
方案1:直接模拟window.location.href的setter(快速适配现有代码)
因为jsdom里的window.location默认是只读对象,我们可以先重定义它的属性,或者用Jest的spyOn拦截setter来绕过导航限制:
describe('YourRedirectComponent', () => { let component: YourRedirectComponent; let fixture: ComponentFixture<YourRedirectComponent>; beforeEach(() => { // 先把window.location改成可写对象,覆盖jsdom的只读限制 Object.defineProperty(window, 'location', { writable: true, value: { href: '' } }); TestBed.configureTestingModule({ declarations: [YourRedirectComponent] }); fixture = TestBed.createComponent(YourRedirectComponent); component = fixture.componentInstance; }); it('should redirect to target URL when condition is satisfied', () => { // 触发你的重定向函数 component.triggerRedirect(); // 断言跳转地址是否正确 expect(window.location.href).toBe('https://your-expected-url.com'); }); });
如果想用spyOn的方式拦截setter,也可以这么写:
beforeEach(() => { jest.spyOn(window.location, 'href', 'set').mockImplementation(() => {}); }); it('should call redirect with correct URL', () => { component.triggerRedirect(); expect(window.location.href.set).toHaveBeenCalledWith('https://your-expected-url.com'); });
方案2:封装导航服务(符合Angular最佳实践)
这个方案稍微需要重构代码,但更符合Angular的依赖注入思想,后续测试和维护会更省心。
首先创建一个导航服务,把直接操作window的逻辑封装起来:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class NavigationService { redirectTo(url: string): void { window.location.href = url; } }
然后在你的组件里注入这个服务,替代直接操作window:
import { NavigationService } from './navigation.service'; @Component({...}) export class YourRedirectComponent { constructor(private navigationService: NavigationService) {} triggerRedirect(): void { if (this.yourCondition) { this.navigationService.redirectTo('https://your-target-url.com'); } } }
测试的时候,只需要mock这个服务即可,完全不用碰window:
describe('YourRedirectComponent', () => { let component: YourRedirectComponent; let fixture: ComponentFixture<YourRedirectComponent>; // 用Jest的Mock类型定义模拟服务 let mockNavigationService: jest.Mocked<NavigationService>; beforeEach(() => { mockNavigationService = { redirectTo: jest.fn() } as jest.Mocked<NavigationService>; TestBed.configureTestingModule({ declarations: [YourRedirectComponent], providers: [ { provide: NavigationService, useValue: mockNavigationService } ] }); fixture = TestBed.createComponent(YourRedirectComponent); component = fixture.componentInstance; }); it('should invoke redirect service with correct URL when condition is met', () => { // 假设条件已满足 component.triggerRedirect(); expect(mockNavigationService.redirectTo).toHaveBeenCalledWith('https://your-target-url.com'); }); });
方案3:修改Jest配置启用导航模拟(不推荐)
部分版本的jsdom支持通过配置开启导航模拟,但Angular 5对应的Jest版本通常比较老旧,这个方案可能不稳定,还容易导致测试用例间的状态污染。如果一定要尝试,可以在jest.config.js里添加:
module.exports = { // 其他已有配置... testEnvironmentOptions: { url: 'http://localhost/', navigation: 'enabled' } };
总结
- 如果你不想大改现有代码,方案1是最快的解决方式;
- 追求代码架构合理性和可维护性,优先选方案2,这也是Angular官方推荐的做法。
内容的提问来源于stack exchange,提问作者David Alsh
相关产品推荐
相关产品推荐

