【问题标题】:How to unit test canActivate of Angular?如何对 Angular 的 canActivate 进行单元测试?
【发布时间】:2020-05-14 17:28:10
【问题描述】:

如何测试 angular 的 canActivate 函数,该函数返回一个函数,该函数又返回一个 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 返回true,我希望这种情况发生。但我不知道从哪里开始?

【问题讨论】:

    标签: angular angular-ui-router angular6 angular7


    【解决方案1】:

    有很多方法可以做到这一点。我会建议类似以下内容。只是为了展示一些多样性,我用一个类模拟了一个服务,用一个 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 中。随意将其 fork 到您自己的 Stackblitz 环境中并进行修改。

    祝你好运。 :)

    【讨论】:

    • 嘿谢谢哥们!!但是当我在我的 vscode 中运行它时,我在第 20 行出现错误,上面写着Argument of type 'LoginMock' is not assignable to parameter of type 'LoginService'. Property 'authService' is missing in type 'LoginMock'.
    • @GauravSingh - 你能告诉我代码吗?你可以在上面 fork 我的 Stackblitz 并更新它,或者给我发送一个指向你的 github 存储库的链接。一些想法:您是否使用Partial&lt;&gt; 声明了 LoginMock?这使得所有属性都是可选的。另一个想法是你在声明 LoginMock 时可能使用了extends 而不是implements,因为extends 引入了现有的constructor()。另一个想法是您的 LoginService 可能有一些需要更多模拟的东西,查看代码会揭示这一点。
    • @GauravSingh - 你有机会整理更多细节吗?
    • 让我们在聊天室里聊天,好吗?如果你可以创建一个聊天室?。
    • 我可以,但我是美国太平洋时间,所以现在是午夜过后。 :)
    【解决方案2】:

    在新冠疫情期间,我在办公室所能做的就是为某些应用程序进行单元测试。我做的最后一个测试是一个 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 的正确测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-07
      • 2019-11-02
      • 1970-01-01
      • 2021-10-07
      • 2017-05-04
      • 1970-01-01
      • 2017-08-08
      • 2020-05-10
      相关资源
      最近更新 更多