Angular2返回Observable的Guard测试方案咨询
测试返回Observable的Angular Guard 实战指南
一、核心思路拆解
测试这类Guard的核心就是把依赖服务完全模拟可控,然后处理Guard返回的异步Observable流,验证它是否按预期返回true/false,或者是否触发了正确的路由跳转。毕竟Guard的canActivate(或canLoad)方法返回的是Observable,必须处理异步逻辑才能拿到结果断言。
二、先搭好测试基础:模拟依赖服务
假设你的Guard依赖了一个AuthService来做权限校验,先看一下典型的Guard和服务代码:
// 示例AuthService @Injectable() export class AuthService { isAuthenticated(): Observable<boolean> { // 实际场景可能是调用后端接口验证登录态 return of(true); } } // 示例AuthGuard @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean | UrlTree> { return this.authService.isAuthenticated().pipe( map(isAuth => { if (isAuth) return true; // 未认证时跳转到登录页 return this.router.parseUrl('/login'); }), catchError(() => of(false)) ); } }
在测试里,我们要用jasmine.createSpyObj来模拟这两个依赖,让它们返回我们预设的结果:
describe('AuthGuard', () => { let guard: AuthGuard; let authServiceSpy: jasmine.SpyObj<AuthService>; let routerSpy: jasmine.SpyObj<Router>; beforeEach(() => { // 创建AuthService的spy,指定要模拟的方法 const authSpy = jasmine.createSpyObj('AuthService', ['isAuthenticated']); // 创建Router的spy,模拟parseUrl方法 const routerMock = jasmine.createSpyObj('Router', ['parseUrl']); TestBed.configureTestingModule({ providers: [ AuthGuard, { provide: AuthService, useValue: authSpy }, { provide: Router, useValue: routerMock } ] }); guard = TestBed.inject(AuthGuard); authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>; routerSpy = TestBed.inject(Router) as jasmine.SpyObj<Router>; }); });
三、逐个写测试用例
1. 用户已认证时,Guard返回true
it('should return true when user is authenticated', async () => { // 让authService的spy返回一个发出true的Observable authServiceSpy.isAuthenticated.and.returnValue(of(true)); // 把Observable转成Promise,用async/await等待结果 const result = await guard.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).toPromise(); expect(result).toBe(true); expect(authServiceSpy.isAuthenticated).toHaveBeenCalled(); });
2. 用户未认证时,Guard返回登录页的UrlTree
it('should redirect to login when user is not authenticated', async () => { const mockUrlTree = {} as UrlTree; // 让router的parseUrl返回我们预设的UrlTree routerSpy.parseUrl.and.returnValue(mockUrlTree); // 让authService返回未认证的结果 authServiceSpy.isAuthenticated.and.returnValue(of(false)); const result = await guard.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).toPromise(); expect(result).toBe(mockUrlTree); expect(routerSpy.parseUrl).toHaveBeenCalledWith('/login'); });
3. 认证请求出错时,Guard返回false
it('should return false when authentication request fails', async () => { // 让authService的spy抛出错误 authServiceSpy.isAuthenticated.and.returnValue(throwError(() => new Error('Auth failed'))); const result = await guard.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).toPromise(); expect(result).toBe(false); });
4. 用fakeAsync/tick处理异步(替代async/await)
如果你习惯用Angular的fakeAsync,可以这么写:
it('should return true when user is authenticated (fakeAsync style)', fakeAsync(() => { authServiceSpy.isAuthenticated.and.returnValue(of(true)); let guardResult: boolean | UrlTree; // 订阅Guard返回的Observable,把结果存到变量里 guard.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).subscribe(res => { guardResult = res; }); tick(); // 触发Observable的异步流,让订阅回调执行 expect(guardResult).toBe(true); }));
四、避坑提醒
- 所有依赖必须模拟:绝对不要用真实的服务,不然测试会依赖外部接口或状态,变得不稳定。
- 一定要处理异步:不管用
async/await还是fakeAsync/tick,必须等Observable发出值后再断言,不然会拿到undefined。 - 验证
UrlTree时,要确保Router的parseUrl被正确调用,而不是只断言结果类型。
内容的提问来源于stack exchange,提问作者Victor Bredihin
相关产品推荐
相关产品推荐

