angular - Cum să testezi un Functional Router Guard cu Parametru în cadrul runInInjectionContext


Implementez un gardian funcțional pentru rutare în Angular 15.2.9, care verifică dacă un utilizator este autentificat. Când acest lucru nu este cazul, gardianul ar trebui să returneze fie false, fie un UrlTree (adică redirecționare către pagina de autentificare), în funcție de parametrul redirectToLogin: boolean.

Folosesc o funcție de fabrică pentru protecția rutelor funcționale similară cu cea din acest articol:

export const isLoggedIn = (redirectToLogin: boolean): CanActivateFn => {
  return (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
    const isLoggedIn = !!inject(CookieService).check('some_cookie');

    if (isLoggedIn) return true;
    if (!isLoggedIn && !redirectToLogin) return false;

    const redirectUrl = inject(Router).createUrlTree(['user', 'sign_in'], {
      queryParams: { next: encodeURIComponent(`/${next.url.join('/')}`) },
      queryParamsHandling: 'merge',
    });
    return redirectUrl;
  };
};

Funcționează așa cum era de așteptat, dar nu pot să scriu specs pentru el:

  let cookieServiceSpy;
  let activatedRouteSnapshot;

  beforeEach(() => {
    cookieServiceSpy = jasmine.createSpyObj<CookieService>(['check']);
    activatedRouteSnapshot = { url: [], data: {} };
    TestBed.configureTestingModule({
      providers: [
        isLoggedIn,
        { provide: CookieService, useValue: cookieServiceSpy },
        {
          provide: ActivatedRouteSnapshot,
          useValue: activatedRouteSnapshot,
        },
      ],
    });
  });

  it('should return true when secure_user_id cookie is present and redirectToLogin is false', () => {
    // given
    cookieServiceSpy.check.and.returnValue(true);
    activatedRouteSnapshot.data.redirectToLogin = false;
    // when
    const result = TestBed.runInInjectionContext(isLoggedIn); // <-- this line is the problem
    // then
    expect(result).toBeTrue();
  });

duce la o eroare TypeScript: Argumentul de tip '(redirectToLogin: boolean) => CanActivateFn' nu poate fi atribuit parametrului de tip '() => CanActivateFn'. Semnătura țintă oferă prea puține argumente. Se aștepta 1 sau mai multe, dar s-a primit 0.

Am încercat și câteva alte opțiuni:

  • TestBed.runInInjectionContext(isLoggedIn as any) ➔ Nu se pot rezolva toți parametrii pentru isLoggedIn: (?). în rularea testului
  • TestBed.runInInjectionContext(isLoggedIn(false)) ➔ Argumentul de tip 'CanActivateFn' nu poate fi atribuit parametrului de tip '() => boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>'. Semnătura țintă oferă prea puțini argumente. Se așteptau 2 sau mai mulți, dar au fost primiți 0.
  • TestBed.runInInjectionContext(isLoggedIn(false) as any) ➔ Nu se pot rezolva toți parametrii pentru isLoggedIn: (?). în rularea testului
  • TestBed.runInInjectionContext(isLoggedIn(false)(activatedRouteSnapshot, null)) ➔ Argumentul de tip 'boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>' nu poate fi atribuit parametrului de tip '() => unknown'. Tipul 'boolean' nu poate fi atribuit tipului '() => unknown'.
  • TestBed.runInInjectionContext(isLoggedIn(false)(activatedRouteSnapshot, null) as any); ➔ inject() trebuie să fie apelat dintr-un context de injectare, cum ar fi un constructor, o funcție de fabrică, un inițializator de câmp sau o funcție folosită cu EnvironmentInjector#runInContext.
  • TestBed.runInInjectionContext(isLoggedIn(false) as any)(activatedRouteSnapshot, null);➔ Această expresie nu poate fi apelată. Tipul ' {} ' nu are semnături de apel.
  • (TestBed.runInInjectionContext(isLoggedIn(false) as any) as any)(activatedRouteSnapshot, null) ➔ Nu pot rezolva toți parametrii pentru isLoggedIn: (?).

Am încercat, de asemenea, să omit funcția de fabrică și să folosesc Datele Rutei ca parametru, așa cum este arătat aici, dar injectarea ActivatedRouteSnapshot nu este posibilă, iar utilizarea ActivatedRoute duce la Observabile în cuib... trebuie să existe o modalitate mai ușoară.

Cum pot testa protecția funcțională a routerului?

Răspuns #1

Eroarea pe care o observați se datorează a două lucruri :

  • Ați furnizat un simbol fără decor, isLoggedIn
  • Prin apelarea TestBed.runInInjectionContext, ai inițializat injectorul și injectorul a încercat să convertească furnizorii în fabrici, ceea ce a eșuat deoarece isLoggedIn nu este anotat și are 1 sau mai mulți parametri (aici doar 1).

Dacă chiar aveai nevoie să injectezi CanActivateFn, ai putea folosi {provide: isLoggedIn, useValue:() => { ... }, dar în cazul nostru nu injectezi funcția, o apelezi doar.

Deci nu este nevoie să furnizați acea funcție și apelul este direct:

  beforeEach(() => {
    cookieServiceSpy = jasmine.createSpyObj<CookieService>(['check']);
    activatedRouteSnapshot = { url: [], data: {} };
    TestBed.configureTestingModule({
      providers: [
        { provide: CookieService, useValue: cookieServiceSpy },
      ],
    });
  });

  it('should return true when secure_user_id cookie is present and redirectToLogin is false', async () => {
    // given
    cookieServiceSpy.check.and.returnValue(true);
    activatedRouteSnapshot.data.redirectToLogin = false;
    // when
    const result = TestBed.runInInjectionContext(() => isLoggedIn(true)(activatedRouteSnapshot, undefined));  // Double call  to retrieve the result. 
    // then
    expect(result).toBeTrue();
  });