I want to test this simple guard both canActivate and canLoad How can manage it ? I did my first step manage the injected store
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanLoad {
constructor(private store: Store<AuthState>) {}
canActivate(): Observable<boolean> {
return this.store.pipe(
select(selectIsAuthenticated),
map(isValidToken => {
if (!isValidToken) {
this.store.dispatch(new Logout());
return false;
}
return true;
}),
take(1)
);
}
canLoad(): Observable<boolean> {
return this.store.pipe(
select(selectIsAuthenticated),
map(isValidToken => {
if (!isValidToken) {
this.store.dispatch(new Logout());
return false;
}
return true;
}),
take(1)
);
}
}
My first step
export const authReducer: ActionReducerMap<{}> = {
status: {}
};
describe('AuthGuard', () => {
let store: Store<{}>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [StoreModule.forRoot({}).forFeature('auth', authReducer)],
providers: [Store, AuthGuard]
});
store = TestBed.get(Store);
});
it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
expect(guard).toBeTruthy();
}));
});
But what about testing canActivate and canLoad ? I've to mock the select and how ?
Testing NgRx facade using override/MockStore I recommend you use MockStore (with/without overrideSelector) to mocking the store state. If you want to involve the selectors in the test (integtration testing them), you should use MockStore : You set the state using the setState method from MockStore .
Test a reducer Reducers are so simple to test as they are pure functions. We just need to call the reducer function and pass in a fake piece of state and an action and then check the new state slice returns.
First we mock authService with a stubbed return value of true . Then we create an instance of our MockRouter and an instance of AuthGuard using our two mocks. We then add our expectation that calling canActivate should return true . Now we'll add our second test case for when canActivate returns false .
Please check comments in the code. To test such classes you don't need TestBed.
describe('AuthGuard', () => {
let guard: AuthGuard;
let store: Subject<any> & any;
// because it's a simple class and
// we don't test templates, inputs, outputs etc,
// we can use simple objects.
beforeEach(() => {
// mocked store can be a simple BehaviorSubject.
store = new BehaviorSubject({});
// and we need a spy of course
store.dispatch = jasmine.createSpy('dispatch');
// now we can create guard
guard = new AuthGuard(store);
});
// don't forget to kill subscriptions.
afterEach(() => {
store.complete();
});
describe('canActivate', () => {
it('logouts on an empty token', () => {
// setting store state we want.
store.next({
authFeatureName: {
isLoggedIn: false,
}
});
// toBeObservable comes from https://www.npmjs.com/package/jasmine-marbles
// it's an awesome tool to test rxjs
// we expect that canActivate will emit false and close the stream - take(1).
expect(guard.canActivate()).toBeObservable(cold('a|', {
a: false,
}));
// also we need to check that an action was dispatched.
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(Logout));
});
it('returns true on valid token', () => {
// setting our store.
store.next({
authFeatureName: {
isLoggedIn: true,
}
});
// check that it emits true now
expect(guard.canActivate()).toBeObservable(cold('a|', {
a: true,
}));
// and that it doesn't dispatch any actions.
expect(store.dispatch).not.toHaveBeenCalled();
});
});
});
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