使用可观察的 urltree 进行 Angular Guard 单元测试

时间:2021-03-09 23:27:15

标签: angular jasmine karma-jasmine

我是该框架中的 Angular 和单元测试的新手。我正在尝试测试我在下面的身份验证保护:

import { AuthenticationService } from '../auth/authentication.service';
import { map, take } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate, CanActivateChild {

  constructor(private authService: AuthenticationService, private router: Router){}
  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    return this.canActivate(childRoute,state,)
  }
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      return this.canProceed()
  }

  private canProceed():  Observable<boolean | UrlTree>{
    return this.authService.user.pipe(
      take(1),
      map( user => {
        const isAuth = !!user;
        if(isAuth) return true;
        return this.router.createUrlTree(['/login'])
      })
    );
  }
}

我很难理解如何测试这个。我做了研究,大多数人模拟路由器对他们的守卫进行测试,但是当我尝试这样做时,我不断收到错误消息。我还没有真正找到任何测试可观察守卫的东西。她是我写的测试。

describe('AuthenticationGuard', () => {
  let injector: TestBed;
  let authService: AuthenticationService;
  let guard: AuthenticationGuard;
  let routeMock: any = { snapshot: {}};
  let routeStateMock: any = { snapshot: {}, url: '/login'};
  let routerMock = {navigate: jasmine.createSpy('navigate')}

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([]),HttpClientTestingModule],
      providers: [{ provide: Router, useValue: routerMock },],
    });
    injector = getTestBed()
    authService = injector.inject(AuthenticationService)
    guard = TestBed.inject(AuthenticationGuard);
  });

  it('should be created', () => {
    expect(guard).toBeTruthy();
  });

  it('should allow the authenticated user to access app', () => {
    expect(true).toEqual(true)
  })

  it('should redirect an unauthenticated user to the login route', () => {
    authService.user = new BehaviorSubject<User | null>(null);
    expect(guard.canActivate(routeMock, routeStateMock)).toEqual(of(false))
    expect(routerMock.navigate).toHaveBeenCalledWith(['/login']);
  })
});

在我的身份验证服务中,我有一个包含用户信息的行为主题,如果用户注销,则更新为 null,如果用户信息已更新,则更新用户信息。 我知道错误是预期的匿名主题。我正在努力了解她发生了什么,任何帮助都会很棒。

1 个答案:

答案 0 :(得分:0)

您可以测试 canActivatecanActivateChild 的返回值或/并测试对 createUrlTree 的调用。您不必期待 router.navigate 的电话。

这是我的尝试。我created a stackblitz通过了测试。

您可以从 canActivatecanActivateChild 订阅重新调整的 Observable 并将您的期望插入其中,不要忘记调用 doneFn 通知 jasmine 测试已完成。

身份验证服务:

import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { User } from "./user";

@Injectable({
  providedIn: "root"
})
export class AuthenticationService {
  /**
   * The  user subject
   */
  private readonly userSubject: BehaviorSubject<User> = new BehaviorSubject(
    null
  );
  // it's good practice to disallow public access to your subjects.
  // so that's why we create this public observable to which components can subscibe.
  public $user = this.userSubject.asObservable();

  constructor() {}
}

AuthGuard:

import { map, take } from 'rxjs/operators';
import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthenticationService } from './auth.service.ts';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationGuard implements CanActivate, CanActivateChild {

  constructor(private authService: AuthenticationService, private router: Router){}
  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    return this.canActivate(childRoute,state,)
  }
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      return this.canProceed()
  }

  private canProceed():  Observable<boolean | UrlTree>{
    return this.authService.$user.pipe(
      take(1),
      map( user => {
        const isAuth = !!user;
        if(isAuth) return true;
        return this.router.createUrlTree(['/login'])
      })
    );
  }
}

AuthGuard 测试:

import { getTestBed, TestBed } from "@angular/core/testing";
import { Router, UrlTree } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing";
import { BehaviorSubject, of } from "rxjs";
import { AuthenticationGuard } from "./auth.guard";
import { AuthenticationService } from "./auth.service";
import { User } from "./user";

describe("AuthenticationGuard", () => {
  let injector: TestBed;
  let authService: AuthenticationService;
  let guard: AuthenticationGuard;
  let routeMock: any = { snapshot: {} };
  let routeStateMock: any = { snapshot: {}, url: "/login" };
  let router: Router;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([])]
    });
    injector = getTestBed();
    authService = injector.inject(AuthenticationService);
    guard = TestBed.inject(AuthenticationGuard);
    router = TestBed.inject(Router);
  });

  it("should be created", () => {
    expect(guard).toBeTruthy();
  });

  it("should allow the authenticated user to access app", () => {
    expect(true).toEqual(true);
  });

  it("should redirect an unauthenticated user to the login route", done => {
    const createUrlTreeSpy = spyOn(router, "createUrlTree").and.callThrough();
    authService.$user = new BehaviorSubject<User | null>(null);
    guard.canActivate(routeMock, routeStateMock).subscribe({
      next: result => {
        console.log("urltree:", result);
        expect(createUrlTreeSpy).toHaveBeenCalledWith(["/login"]);
        done();
      },
      error: err => {
        console.log("i don't end up here");
        done();
      }
    });
  });

  it("should return true and not redirect", done => {
    const createUrlTreeSpy = spyOn(router, "createUrlTree").and.callThrough();
    authService.$user = new BehaviorSubject<User | null>(new User());
    guard.canActivate(routeMock, routeStateMock).subscribe({
      next: result => {
        console.log("result:", result);
        expect(result).toBeTruthy();
        expect(createUrlTreeSpy).not.toHaveBeenCalledWith(["/login"]);
        done();
      },
      error: err => {
        console.log("i don't end up here");
        done();
      }
    });
  });
});