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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 03:47:32