Угловой - Тестирование Routerguard - PullRequest
0 голосов
/ 04 сентября 2018

В настоящее время я борюсь за модульное тестирование моего метода canActivate() из моей службы Routerguard. Сервис выглядит следующим образом:

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router} from '@angular/router';
import {AuthService} from '../../auth/auth.service';
import {Observable, of} from 'rxjs';
import {NotificationService} from '../../../../shared/services/notification.service';
import {concatMap, map, take, tap} from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ProfileGuard implements CanActivate {

  constructor(private auth: AuthService, private router: Router,
              private notification: NotificationService) {
  }

  canActivate(next: ActivatedRouteSnapshot): Observable<boolean> {
    // checks for user if not - page not found
    return this.auth.getUserEntity(next.params.uid).pipe(concatMap(user => {
      if (user) {
        // checks for permission if not - redirect to user overview
          return this.auth.currentUser.pipe(
            take(1),
            map(current => this.auth.canEditProfile(current, next.params)),
            tap(canEdit => {
              if (!canEdit) {
                this.router.navigate([`/profile/${next.params.uid}`]).then(() =>
                  this.notification.danger('Access denied. Must have permission to edit profile.'));
              }
            })
          );
      } else {
        this.router.navigate(['/page-not-found']);
        return of(false);
      }
    }));
  }
}

На самом деле это выглядит сложнее, чем: Первый наблюдатель проверяет, есть ли в БД пользователь со значением params в качестве уникального идентификатора. Затем второй наблюдатель проверяет разрешение на редактирование этого пользователя. Теперь о модульном тестировании часть вещей:

describe('RouterGuardService', () => {

  const routerStub: Router = jasmine.createSpyObj('Router', ['navigate']);
  const authStub: AuthService = jasmine.createSpyObj('AuthService', ['getUserEntity', 'currentUser', 'canEditProfile']);
  const notificationStub: NotificationService = jasmine.createSpyObj('NotificationService', ['danger']);

  function createInputRoute(url: string): ActivatedRouteSnapshot {
    const route: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
    const urlSegs: UrlSegment[] = [];
    urlSegs.push(new UrlSegment(url, {}));
    route.url = urlSegs;
    route.params = {
      uid: url.replace('/profile/', '')
        .replace('/edit', '')
    };
    return route;
  }

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        {provide: AuthService, useValue: authStub},
        {provide: Router, useValue: routerStub},
        {provide: NotificationService, useValue: notificationStub},
        ProfileGuard]
    });
  });

 it('should redirect to user overview - if has not permission', inject([ProfileGuard], (service: ProfileGuard) => {
  (<jasmine.Spy>authStub.canEditProfile).and.returnValue(false);
  authStub.currentUser = of(<any>{uid: 'jdkffdjjfdkls', role: Role.USER});
  (<jasmine.Spy>authStub.getUserEntity).and.returnValue(of({uid: 'jdkffdjjfdkls', role: Role.USER}));

  const spy = (<jasmine.Spy>routerStub.navigate).and.stub();
  const notifySpy = (<jasmine.Spy>notificationStub.danger).and.stub();

  const url: ActivatedRouteSnapshot = createInputRoute('/profile/BBB/edit');
  service.canActivate(url).subscribe(res => {
    console.log(res);
    expect(spy).toHaveBeenCalledWith(['/BBB']);
    expect(notifySpy).toHaveBeenCalledWith('Access denied. Must have permission to edit profile.');
    expect(res).toBe(false);
  }, err => console.log(err));
}));
});

Но мой тест не проверяет мои ожидаемые методы, вместо этого консоль регистрирует ошибку. Может кто-нибудь помочь мне в этом?

1 Ответ

0 голосов
/ 07 сентября 2018

Первая проблема - при создании authStub:

const authStub: AuthService = jasmine.createSpyObj('AuthService', ['getUserEntity', 'currentUser', 'canEditProfile']);

В этом случае вы добавляете currentUser как метод, но не как свойство. Правильный способ создать jasmine spyObj как с методами, так и со свойствами :

  const authStub = {
    ...jasmine.createSpyObj('authStub', ['getUserEntity', 'canEditProfile']),
    currentUser: of(<any>{ uid: 'jdkffdjjfdkls', role: Role.USER })
  } as jasmine.SpyObj<AuthService>;

Обратите внимание , что в вашем примере - эта мутация объекта внутри теста ни на что не влияет:

authStub.currentUser = of(<any>{uid: 'jdkffdjjfdkls', role: Role.USER});

Причина в том, что вы используете useValue при предоставлении услуг для TestBed, и это означает, что тесты уже получили экземпляр службы аутентификации, который не имеет свойства currentUser. Вот почему важно инициализировать его перед запуском метода configureTestingModule.

Вторая проблема - поскольку ваш защитный код асинхронный, вы должны писать свои модульные тесты асинхронно (вы можете использовать done, sync или fakeAsync&tick).

Вот окончательное решение:

describe('RouterGuardService', () => {
  const routerStub: Router = jasmine.createSpyObj('Router', ['navigate']);

  const authStub = {
    ...jasmine.createSpyObj('authStub', ['getUserEntity', 'canEditProfile']),
    currentUser: of(<any>{ uid: 'jdkffdjjfdkls', role: Role.USER })
  } as jasmine.SpyObj<AuthService>;

  const notificationStub: NotificationService = jasmine.createSpyObj('NotificationService', ['danger']);

  let profileGuardService: ProfileGuard;

  function createInputRoute(url: string): ActivatedRouteSnapshot {
    // ...
  }

  beforeEach(() => {
    TestBed.configureTestingModule({
      // ...
    });
    profileGuardService = TestBed.get(ProfileGuard);
  });

  it('should redirect to user overview - if has not permission', fakeAsync(() => {
    (<jasmine.Spy>authStub.canEditProfile).and.returnValue(false);
    (<jasmine.Spy>authStub.getUserEntity).and.returnValue(of({ uid: 'jdkffdjjfdkls', role: Role.USER }));

    const spy = (<jasmine.Spy>routerStub.navigate).and.callFake(() => Promise.resolve());
    const notifySpy = (<jasmine.Spy>notificationStub.danger).and.stub();

    const url: ActivatedRouteSnapshot = createInputRoute('/profile/BBB/edit');
    let expectedRes;
    profileGuardService.canActivate(url).subscribe(res => {
      expectedRes = res;
    }, err => console.log(err));

    tick();
    expect(spy).toHaveBeenCalledWith(['/profile/BBB']);
    expect(notifySpy).toHaveBeenCalledWith('Access denied. Must have permission to edit profile.');
    expect(expectedRes).toBe(false);
  }));
});

Если вы хотите иметь разные currentUser для каждого теста динамически, вы можете сделать этот трюк - инициализировать свойство currentUser в authStub с BehaviorSubject:

const authStub = {
  ...jasmine.createSpyObj('authStub', ['getUserEntity', 'canEditProfile']),
  currentUser: new BehaviorSubject({})
} as jasmine.SpyObj<AuthService>;

А затем внутри самих модульных тестов вы можете вызвать метод next, чтобы установить необходимый текущий пользовательский макет:

it('should redirect to user overview - if has not permission', fakeAsync(() => {
  (<BehaviorSubject<any>>authStub.currentUser).next(<any>{ uid: 'jdkffdjjfdkls', role: Role.USER });
  // ...
...