如何对 Angular 的 canActivate 进行单元测试?

How to unit test canActivate of Angular?

我如何测试 angular 的 canActivate 函数,returns 函数又 returns 一个 boolean 值?

我尝试创建 ActivatedrouterSnapshotrouterStateSnapshot 的对象并将其传递给 canActivate 函数,但这没有帮助。

export class AuthGuard implements CanActivate {
constructor(
private authService: AuthenticationService,
private loginService: LoginService,
private router: Router
) {}

canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> 
| boolean {
return this.checkLogin(state.url);
}

checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn()) {  return true; }

// Store the attempted URL for redirecting
this.loginService.redirectUrl = url;

// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
 }
}

自从 checklogin returns true 以来,我希望它发生。但是我不知道从哪里开始?

有很多方法可以做到这一点。我建议如下。为了展示一些多样性,我用 class 模拟了一个服务,用 spyObject.

模拟了另一个服务

这是建议的代码:

class LoginMock implements Partial<LoginService> {
    redirectUrl: string;
}

describe('AuthGuard', () => {

    let authGuard: AuthGuard;
    let loginService: LoginMock;
    const routerMock = jasmine.createSpyObj('Router', ['navigate']);
    const authMock = jasmine.createSpyObj('AuthenticationService', ['isLoggedIn']);

    beforeEach(() => {
        loginService = new LoginMock();
        authGuard = new AuthGuard(authMock, loginService, routerMock);
    });

    it('should be createable', () => expect(authGuard).toBeTruthy());

    it('should return true for canActivate() and not set loginService.redirectUrl when isLoggedIn === true', ()=> {
        authMock.isLoggedIn.and.returnValue(true);
        const result = authGuard.canActivate(new ActivatedRouteSnapshot(), <RouterStateSnapshot>{url: 'testUrl'});
        expect(result).toBe(true);
        expect(loginService.redirectUrl).toBeUndefined();
    });

    it('should return false for canActivate() and set loginService.redirectUrl when isLoggedIn === false', ()=> {
        authMock.isLoggedIn.and.returnValue(false);
        const result = authGuard.canActivate(new ActivatedRouteSnapshot(), <RouterStateSnapshot>{url: 'testUrl'});
        expect(result).toBe(false);
        expect(loginService.redirectUrl).toEqual('testUrl');
    });

});

我已将其整理成 Stackblitz 供您参考。随意将其分叉到您自己的 Stackblitz 环境中并进行修改。

祝你好运。 :)

疫情期间我在办公室所能做的就是为一些应用程序做单元测试。我做的最后一个测试是一个 canActivate 服务,它看起来像这样:

import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
import {UserProvider} from "../core/app-initializers/user-provider.service";
import {permissionsList} from "../shared/models/permissions.model";

@Injectable()
export class CreateArrangementPermissionService implements CanActivate {
  constructor(private router: Router, private userProvider: UserProvider) {}
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    if(route.params.idOrCreationKeyWord && route.params.idOrCreationKeyWord == 'create') {
      if (this.userProvider.getConnectedUsersApplicativeRoles().indexOf(permissionsList.DECLARANT) < 0) {
        this.router.navigate(['/forbidden']);
        return false;
      }
    }
    return true;
  }
}

而且我已经编写了测试:

import { CreateArrangementPermissionService } from "./create-arr-permission-service";
import { TestBed, getTestBed } from "@angular/core/testing";
import { UserProvider } from "../core/app-initializers/user-provider.service";
import { Router } from "@angular/router";

describe('CreateArrangementPermissionService Unit tests', () => {
    let injector: TestBed;
    let userProvider = {
        getConnectedUsersApplicativeRoles: function() {
            return 'zorro'
        }
    };

    let guard: CreateArrangementPermissionService;
    let routeMock: any = { 
        snapshot: {},
        params: {
            idOrCreationKeyWord: 'create'
        }
    };

    let routerStateMock: any = {
        snapshot: {},
        url: '/forbidden'
    };

    let routerMock = {
        navigate: jasmine.createSpy('navigate')
    };

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                { provide: UserProvider, useValue: userProvider},
                { provide: Router, useValue: routerMock },
                AuthenticationErrorHandler,
                AuthenticationErrorCommunicator,
                CreateArrangementPermissionService,
            ]
        });
        injector = getTestBed();
        userProvider = injector.get(UserProvider);
        guard = injector.get(CreateArrangementPermissionService)
    });

    it('should redirect an unauthenticated user to forbidden', () => {
        expect(guard.canActivate(routeMock, routerStateMock)).toEqual(false);
        expect(routerMock.navigate).toHaveBeenCalledWith(['/forbidden'])
    });

    it('should allow the authenticated user to access app', () => {
        let routeMockAdmin: any = { 
            snapshot: {},
            params: {
                idOrCreationKeyWord: 'TPRM_ADMINISTRATOR'
            }
        };
        expect(guard.canActivate(routeMockAdmin, routerStateMock)).toEqual(true);
    });

});

通过这个测试,我在 SonarQube 上获得了 89.5% 的覆盖率,缺少一些针对构造函数和 "indexOf()" 的测试。 希望这个例子能帮助其他人更容易地正确测试 canActivate,然后我自己试验。