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
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With