Angular应用单元测试:Jasmine处理外部重定向方案咨询
解决Angular SSO登录重定向导致单元测试中断的问题
我之前也碰到过类似的烦恼——每次为了跑单元测试要注释重定向代码,部署前还要改回来,简直是重复劳动还容易出错。其实咱们可以通过依赖注入封装或者全局对象Mock的方式,彻底解决这个问题,不用再手动修改业务代码啦!
方案一:封装Window服务(推荐,符合Angular最佳实践)
直接操作全局的window对象是测试不友好的,咱们可以把它封装成一个可注入的服务,这样在测试时就能轻松Mock它的行为。
1. 创建Window封装服务
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class WindowService { get nativeWindow(): Window { return window; } }
2. 修改AuthGuard,注入WindowService
把原来直接调用window.location.href的逻辑,换成调用封装服务的方法:
@Injectable() export class AuthGuard implements CanActivate { constructor( private router: Router, private location: Location, private windowService: WindowService // 注入封装的服务 ) { } canActivate(): boolean { if (!localStorage.getItem('API_TOKEN')) { this.redirectTOLogin(); return false; } else { return true; } } redirectTOLogin() { // 改用封装的window对象 this.windowService.nativeWindow.location.href = environment.appLoginUrl + environment.appUrl; } }
3. 单元测试中Mock WindowService
在测试用例里,我们可以创建一个Mock的WindowService,拦截重定向操作,只验证它是否被调用,而不会真的跳转:
describe('AuthGuard', () => { let authGuard: AuthGuard; let mockWindowService: jasmine.SpyObj<WindowService>; const router = { navigate: jasmine.createSpy('navigate') }; beforeEach(async(() => { // 创建Mock的WindowService,包含nativeWindow的spy mockWindowService = jasmine.createSpyObj('WindowService', [], { nativeWindow: jasmine.createSpyObj('Window', [], { location: { href: '' } }) }); TestBed.configureTestingModule({ imports: [FormsModule, CommonModule], providers: [ AuthGuard, Location, { provide: LocationStrategy, useClass: PathLocationStrategy }, { provide: APP_BASE_HREF, useValue: '/' }, { provide: Router, useValue: router }, { provide: WindowService, useValue: mockWindowService } // 注入Mock服务 ] }).compileComponents(); })); beforeEach(() => { authGuard = TestBed.inject(AuthGuard); // 每次测试前清空localStorage localStorage.clear(); }); it('should redirect to login page when API_TOKEN is missing', () => { // 执行守卫逻辑 const result = authGuard.canActivate(); // 验证返回false expect(result).toBeFalse(); // 验证重定向的URL是否正确设置 expect(mockWindowService.nativeWindow.location.href).toBe(environment.appLoginUrl + environment.appUrl); }); it('should allow access when API_TOKEN exists', () => { // 设置localStorage localStorage.setItem('API_TOKEN', 'test-token'); const result = authGuard.canActivate(); expect(result).toBeTrue(); // 验证没有触发重定向 expect(mockWindowService.nativeWindow.location.href).not.toBe(environment.appLoginUrl + environment.appUrl); }); });
方案二:直接Mock全局window对象(快速解决,适合小场景)
如果不想额外创建服务,也可以直接在测试中拦截window.location.href的赋值操作。注意window.location.href是只读属性,直接用spyOn不行,需要通过Object.defineProperty来拦截:
describe('AuthGuard', () => { let authGuard: AuthGuard; const router = { navigate: jasmine.createSpy('navigate') }; let originalHref: string; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FormsModule, CommonModule], providers: [ AuthGuard, Location, { provide: LocationStrategy, useClass: PathLocationStrategy }, { provide: APP_BASE_HREF, useValue: '/' }, { provide: Router, useValue: router } ] }).compileComponents(); })); beforeEach(() => { authGuard = TestBed.inject(AuthGuard); localStorage.clear(); // 保存原始的href,测试后恢复 originalHref = window.location.href; // 拦截window.location.href的setter Object.defineProperty(window.location, 'href', { writable: true, value: originalHref }); }); // 测试后恢复原始的href afterEach(() => { Object.defineProperty(window.location, 'href', { writable: false, value: originalHref }); }); it('should redirect to login page when API_TOKEN is missing', () => { const expectedUrl = environment.appLoginUrl + environment.appUrl; authGuard.canActivate(); expect(window.location.href).toBe(expectedUrl); expect(authGuard.canActivate()).toBeFalse(); }); });
两种方案对比
- 方案一:更符合Angular的依赖注入思想,代码可维护性更高,后续如果有其他需要操作window的逻辑,都可以通过这个服务统一处理,测试也更灵活。
- 方案二:无需修改业务代码,快速实现测试拦截,但全局Mock可能会影响其他测试用例,需要注意在测试前后恢复原始状态。
内容的提问来源于stack exchange,提问作者Govinda raj
相关产品推荐
相关产品推荐

