web-dev-qa-db-fra.com

Jasmine Unit test - Impossible de lire le pipe de propriété d'undefined

J'utilise Angular 6, NgRx 6, RxJS 6.

J'ai un garde de route qui ressemble à ceci -

import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { IAppState } from '../../../app.state';
import { Store } from '@ngrx/store';

import { SetTenant } from './../../../store/config/config.actions';

@Injectable()
export default class TenantGuard implements CanActivate {
  constructor(private store: Store<IAppState>) {}

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const tenant = route.params['tenant'];

    if (!tenant) {
      return of(false);
    }

    this.store.dispatch(new SetTenant(tenant));
    return of(true);
  }
}

Comme vous pouvez le constater, j’ajoutais ma tenant au magasin via this.store.dispatch(new SetTenant(tenant));

Cela provoquait toutefois cette action à chaque fois qu'un utilisateur visitait la route de base.

Afin de lutter contre cela, j'ai ajouté une vérification pour voir si la tenant est remplie et ne déclenche que l'action sinon -

import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable, of, combineLatest } from 'rxjs';
import { IAppState } from '../../../app.state';
import { Store, select } from '@ngrx/store';

import { SetTenant } from './../../../store/config/config.actions';
import { getTenant } from '../../../store/config/config.selectors';
import { map } from 'rxjs/operators';

@Injectable()
export default class TenantGuard implements CanActivate {
  constructor(private store: Store<IAppState>) {}

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const tenantFromRoute: string = route.params['tenant'];

    return this.store.pipe(select(getTenant)).pipe(
      map(tenantFromStore => {
        if (!tenantFromRoute) {
          return false;
        }

        if (!tenantFromStore) {
          this.store.dispatch(new SetTenant(tenantFromRoute));
        }
        return true;
      })
    );
  }
}

Cela a toutefois cassé mes tests unitaires car j'ai introduit une logique supplémentaire et je reçois maintenant l'erreur TypeError: Cannot read property 'pipe' of undefined

Mon fichier de spécifications ressemble à ceci -

import { TestBed, async } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { Store } from '@ngrx/store';
import { StoreModule } from '@ngrx/store';

import { SetTenant } from './../../../store/config/config.actions';

import TenantGuard from './tenant.guard';

describe('TenantGuard', () => {
  it('should return false if a tenant is not present on the route', async(() => {
    const { tenantGuard, props } = setup({});
    let result: boolean;
    tenantGuard.canActivate(props).subscribe(canActivate => (result = canActivate));
    expect(result).toBeFalsy();
  }));

  it('should return true if a tenant is present on the route', async(() => {
    const { tenantGuard, props } = setup({ tenant: 'main' });
    let result: boolean;
    tenantGuard.canActivate(props).subscribe(canActivate => (result = canActivate));
    expect(result).toBeTruthy();
  }));

  it('should dispatch an action to set the tenant in the store', () => {
    const { store, tenantGuard, props } = setup({ tenant: 'foo' });
    const action = new SetTenant('foo');
    tenantGuard.canActivate(props);

    expect(store.dispatch).toHaveBeenCalledWith(action);
  });

  it('should not dispatch an action to set the tenant in the store if the tenant is missing', () => {
    const { store, tenantGuard, props } = setup({});
    tenantGuard.canActivate(props);

    expect(store.dispatch).not.toHaveBeenCalled();
  });

  const setup = propOverrides => {
    TestBed.configureTestingModule({
      imports: [StoreModule.forRoot({})],
      providers: [
        TenantGuard,
        {
          provide: Store,
          useValue: jasmine.createSpyObj('Store', ['dispatch', 'pipe']),
        },
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
    }).compileComponents();

    const props = Object.assign({ params: { tenant: null } }, { params: { ...propOverrides } });

    const tenantGuard = TestBed.get(TenantGuard);
    const store = TestBed.get(Store);

    return { tenantGuard, props, store };
  };
});

J'ai ajouté pipe à mon jasmine.createSpyObj mais je ne sais pas comment progresser.

J'aimerais écrire des tests supplémentaires à ce sujet, mais j'ai du mal à comprendre comment pipe devrait/devrait être utilisé dans ce cas.

Edit - Si je ne passe pas pipe à mon jasmine.createSpyObj, j'obtiens à la place l'erreur ​​TypeError: this.store.pipe is not a function​​

5
Harry Blue

J'ai eu le même message d'erreur que vous. J'ai injecté le routeur dans un composant et utilisé le this.router.events.pipe (...) ... J'utilise un tronçon pour le routeur dans mon test. Avant le routeurStub ressemblait à ceci:

routerStub = {
        navigate: (commands: any[]) => { Promise.resolve(true); },
};

Ainsi, vous pouvez voir que, avant d’avoir besoin de la méthode de navigation du routeur dans mon composant, je l’ai définie dans le stub. Maintenant, j'ai aussi besoin de la propriété events, qui renvoie un observable où .pipe est utilisé. J'ai ajouté ce qui suit au routerStub qui l'a corrigé pour moi:

routerStub = {
        navigate: (commands: any[]) => { Promise.resolve(true); },
        events: of(new Scroll(new NavigationEnd(0, 'dummyUrl', 'dummyUrl'), [0, 0], 'dummyString'))
};

Dans mon cas, j'ai besoin d'un événement de défilement pour que mon code fonctionne, mais les événements sont maintenant définis dans mon stub en tant qu'observable et le canal est maintenant connu.

Peut-être que cela vous aide ...

0
Gerros