Jasmine Unit test - Cannot read property pipe of undefined

20,834

I had the same error message like you. I injected the router in a component and used the this.router.events.pipe(...)... I use a stub for the router in my test. Before the routerStub looked like this:

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

So you can see that before I need the navigate method of the router in my component therefore I defined it in the stub. Now I need the events property as well which returns an observable where than .pipe is used. I added following to the routerStub which fixed it for me:

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

In my case I need a Scroll Event for my code to work, but now events is defined in my stub as an Observable and now pipe is known.

Maybe this helps you...

Share:
20,834

Related videos on Youtube

Harry Blue
Author by

Harry Blue

Updated on July 18, 2022

Comments

  • Harry Blue
    Harry Blue almost 2 years

    I am using Angular 6, NgRx 6, RxJS 6.

    I have a route guard that looked like this -

    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);
      }
    }
    

    As you can see, I was adding my tenant to the store via this.store.dispatch(new SetTenant(tenant));

    This was however causing that action to fire every time a user visited the base route.

    In order to combat this, I have added a check to see if the tenant is populated and only fire the action if not -

    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;
          })
        );
      }
    }
    

    This has however broken my unit tests as I have introduced additional logic and I am now getting the error TypeError: Cannot read property 'pipe' of undefined

    My spec file looks like this -

    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 };
      };
    });
    

    I have added pipe to my jasmine.createSpyObj however I am unsure how to progress.

    I would like to write additional tests around this, but am having trouble mocking out how pipe should / would be used in this case.

    Edit - If I do not pass pipe in to my jasmine.createSpyObj I instead get the error ​​TypeError: this.store.pipe is not a function​​

    • Harry Blue
      Harry Blue almost 6 years
      So I think the issue might be in how I am mocking my store.
    • Vivek Singh
      Vivek Singh over 5 years
      Did this error got resolved..??
  • Ben Racicot
    Ben Racicot over 5 years
    I'm getting the pipe error with events just as you show
  • Fatih Ersoy
    Fatih Ersoy about 2 years
    I also get the error : router.events.pipe is not a function