0

我对 Angular 7 比较陌生,来自 AngularJS,我编写了一个实现 CanLoad 的守卫,它在没有正确声明加载模块的情况下阻止用户。它检查用户是否已登录以及用户是否有路由所期望的声明。

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoadGuard } from './core/authentication/guards/load.guard';
import { MainMenuComponent } from './core/navigation/main-menu/main-menu.component';
import { PageNotFoundComponent } from './core/navigation/page-not-found/page-not-found.component';
import { UnauthorisedComponent } from './core/navigation/unauthorised/unauthorised.component';

const routes: Routes = [
  { path:'', component: MainMenuComponent, outlet: 'menu'},
  { path: 'authentication', loadChildren: './core/authentication/authentication.module#AuthenticationModule' },
  { path: 'home', loadChildren: './areas/home/home.module#HomeModule', canLoad: [LoadGuard], data: {expectedClaim: 'home'} },
  { path:"unauthorised", component: UnauthorisedComponent},
  { path:'**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

守卫工作,但是我在为它编写单元测试时遇到了麻烦。

import { Injectable } from '@angular/core';
import { CanLoad, Route, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthenticationService } from 'src/app/Services/Authentication/authentication.service';

@Injectable({
  providedIn: 'root'
})
export class LoadGuard implements CanLoad {

  constructor(private authService: AuthenticationService, private router: Router){}

  canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean {
    if (!route || !route.path) return false;
    let isValid: boolean = this.checkLoggedIn(route.path);
    if (isValid) {
      if (route.data && route.data.expectedClaim) {
        let expectedClaim = route.data.expectedClaim;
        isValid = this.checkClaim(expectedClaim);
      }
    }
    return isValid;
  }

  checkLoggedIn(url: string): boolean {
    if (this.authService.checkLoggedIn()) {
      return true;
    }
    this.authService.redirectUrl = url;
    console.log('this.authService.redirectUrl (after)= ' + this.authService.redirectUrl);
    this.router.navigate(['/authentication/login']);
    return false;
  }

  checkClaim(claim: string) {
    let hasClaim: boolean = false;
    if (this.authService.currentUser) {
      hasClaim = this.authService.currentUser.claims.indexOf(claim) > -1;
    }
    return hasClaim;
  }

}

我在下面的单元测试不起作用:

import { HttpClientModule } from '@angular/common/http';
import { fakeAsync, TestBed } from '@angular/core/testing';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, ActivatedRoute, Route } from '@angular/router';
import { LoadGuard } from './load.guard';

class MockActivatedRouteSnapshot {
  private _data: any;
  get data(){
     return this._data;
  }
}

let mockRouterStateSnapshot : RouterStateSnapshot;

describe('LoadGuard', () => {
  let loadGuard: LoadGuard;
  let route: ActivatedRouteSnapshot;
  let authService;
  let mockRouter: any;  

  beforeEach(() => {
    mockRouter = jasmine.createSpyObj('Router', ['navigate']);

    TestBed.configureTestingModule({
      imports: [
        HttpClientModule,
      ],
      providers: [
        LoadGuard,
        { provide: ActivatedRouteSnapshot, useClass: MockActivatedRouteSnapshot},
        { provide: Router, useValue: mockRouter},
      ]
    });

  });


  it('should be created', () => {
    authService = { checkLoggedIn: () => true };
    loadGuard = new LoadGuard(authService, mockRouter);
    expect(loadGuard).toBeTruthy();
  });

  describe('check expected claims', ()=>{
    it('should not be able to load an valid route needing claim when logged in without claim', fakeAsync(() => {
      authService = { checkLoggedIn: () => true };
      loadGuard = new LoadGuard(authService, mockRouter);

      let route = new Route();
      spyOnProperty(route,'data','get').and.returnValue({expectedClaim: 'policy'});
      mockRouterStateSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
      mockRouterStateSnapshot.url = "test";

      expect(loadGuard.canLoad(route)).toBeFalsy();
    }));

});

它不允许我新建一条路线。我可能只是做错了测试。有人能帮忙吗?

4

1 回答 1

3

测试 canLoad()

与 不同canActivate()canLoad()只需要一个 Route 参数。即canLoad(route: Route):booleancanActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot )Route只是我们用来定义和导出路由的接口,它应该已经存在于 TestBed 模块上下文中。因此,您根本不需要模拟它或创建它的新实例。


在你的 beforeEach(async()) jasmine 函数中,使用路由导入 RouterTestingModule。

   TestBed.configureTestingModule({
      imports: [HttpClientModule, ... ..., RouterTestingModule.withRoutes(routes)],
      ...
      providers: [AuthenticationService, LoadGuard ] //only need to provide it here! 
   })

是你用and定义routes的那个。export const routes: Routes = [{}]loadChildrencanLoad

请注意这一点,导入 RouterTestingModule 会自动提供(即注入)这些服务:

  1. 地点
  2. 位置策略
  3. NgModuleFactoryLoader
  4. 预加载策略
  5. 路由器

可以在这个 API 文档链接中看到:https ://angular.io/api/router/testing/RouterTestingModule#providers

因此,您可以简单地引用这些注入的服务,而不必像以前那样模拟它们。

在您的 describe() jasmine 函数中,声明这些:

describe('AppComponent', () => {
   ... //all your other declarations like componentFixture, etc
   loadGuard: LoadGuard; 
   authService: AuthenticationService;
   router: Router;
   location: Location;
   loader: NgModuleFactoryLoader;
   ...
});

在你的 beforeEach() jasmine 函数中:

location = TestBed.get(Location); //these already exist in TestBed context because of RouterTestingModule
router = TestBed.get(Router);
... //other declarations
loadGuard = TestBed.get(LoadGuard);
authService = TestBed.get(AuthenticationService);

现在,这个单元测试用例的目标是测试路由以及是否实际加载了相应的模块。

因此,您还需要在 beforeEach() jasmine 函数中对延迟加载的模块进行存根:

loader = TestBed.get(NgModuleFactoryLoader); //already exists in TestBed context because of RouterTestingModule

loader.stubbedModules = {
   './areas/home/home.module#HomeModule': HomeModule,
   ... //other lazily loaded modules 
}

fixture.detectChanges();

由于您已经configureTestingModule()按照上述方法导入了路由,因此您无需像 API 规范那样重置路由器配置(https://angular.io/api/router/testing/SpyNgModuleFactoryLoader#description)。

完成所有设置后,您就可以测试您的 canLoad() 守卫了。

it('if X claims is false, user shouldn't be able to load module', fakeAsync(() => {
   spyOn(authService, 'checkClaims').and.returnValue(false);
   fixture.detectChanges();

   router.navigateByUrl('XRoute');
   tick();
   expect(location.path()).toBe('/"unauthorised'); //or /404 depending on your req
}))

您不需要模拟其他任何东西或创建间谍对象等等。

虽然这个答案晚了几个月,而且很可能,你现在甚至不需要它。我希望通过发布它来帮助其他 Angular 开发人员,因为这是 Stackoverflow 中唯一一个专门询问我也遇到过的单元测试 canLoad() 的问题。

于 2019-07-22T09:52:16.047 回答