Модульное тестирование CanLoad - PullRequest
0 голосов
/ 18 апреля 2019

Я относительно новичок в 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();
    }));

});

Он не позволяет мне создать новый маршрут.Я могу просто сделать тест неправильно.Кто-нибудь может помочь с этим?

1 Ответ

0 голосов
/ 22 июля 2019

Тестирование canLoad ()

В отличие от canActivate(), canLoad() требует только аргумента Route. То есть canLoad(route: Route):boolean против canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ). Route - это просто интерфейс, который мы используем для определения и экспорта маршрутов, и он уже должен существовать в контексте модуля TestBed. Следовательно, вам вообще не нужно издеваться над ним или создавать новый экземпляр.


В вашей функции жасмина beforeEach (async ()) импортируйте RouterTestingModule с маршрутами.

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

с routes является export const routes: Routes = [{}], который вы определили с loadChildren и canLoad.

Обратите внимание на это, импортируя RouterTestingModule автоматически предоставляет (то есть внедряет) следующие услуги:

  1. Расположение
  2. LocationStrategy
  3. NgModuleFactoryLoader
  4. PreloadingStrategy
  5. Маршрутизатор

Как видно из этой ссылки на документы API: https://angular.io/api/router/testing/RouterTestingModule#providers

Из-за этого вы можете просто ссылаться на эти введенные услуги без насмешки над ними , как вы это сделали.

В вашей функции description () jasmine объявите следующее:

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

В вашей функции beforeEach () с жасмином:

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 (), с которым я тоже столкнулся.

...