I'm creating unit test for my Angular app but i don'k know create tests for my custom guard that use Redux.
This is my code of the guard
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot, UrlSegment, UrlTree } from '@angular/router';
import { first, map, Observable, tap } from 'rxjs';
import { GlobalState } from '../state/app.reducer';
import { Store } from '@ngrx/store';
import { RolesAccount } from 'src/app/pages/auth/interfaces/auth.constant';
@Injectable({
providedIn: 'root'
})
export class AdministratorGuard implements CanActivate, CanLoad {
constructor(private route: Router,private store: Store<GlobalState>) {}
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.isAdmin()
}
canLoad( route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.isAdmin()
}
isAdmin():Observable<true | UrlTree>{
return this.store.select('authentication').pipe(
first(), // take of first value
map(userStore => userStore.userLogged?.role || ''),
//TODO we hardcode the email of administrator until role is in JWT
map(role => role === RolesAccount.ADMIN ? true : this.route.createUrlTree(['/home']))
);
}
}
I run npm run coverage
And this is the uncovered block (I need create unit test for canActivate, canLoad, and isAdmin
And this is my unit testing file (default testing)
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AuthComponent } from 'src/app/pages/auth/auth.component';
import { IUserState } from 'src/app/pages/auth/interfaces/auth.interfaces';
import { AdministratorGuard } from '../administrator.guard';
fdescribe('AdministratorGuard', () => {
let guard: AdministratorGuard;
let store: MockStore<IUserState>;
beforeEach(() => {
TestBed.configureTestingModule({
imports:[
RouterTestingModule.withRoutes(
[
{
path: 'auth',
component: AuthComponent
},
]
),
],
providers:[
provideMockStore({}),
]
});
guard = TestBed.inject(AdministratorGuard);
store = TestBed.inject(MockStore);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});
Thanks in advance
CodePudding user response:
With how you have it defined you would need to add your guard to the auth path that you have defined.
RouterTestingModule.withRoutes(
[
{
path: 'auth',
canActivate: [AdministratorGuard],
component: AuthComponent
},
]),
Then in your it
you would need to route to your 'auth' path to fire that guard.
Now, I don't like this approach for the following reason - you are testing that the RouterTestingModule behaves like the real RouterModule.
Instead I would suggest testing your guard like a service.
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AdministratorGuard,
provideMockStore({}),
]
});
guard = TestBed.inject(AdministratorGuard);
store = TestBed.inject(MockStore);
});
it('should allow canActivate', (done) => {
// ... setup to allow it to return true
guard.canActivate(null, null).subscribe(x => {
// code to make
expect(x).toBe(true);
done();
});
});
it('should deny canActivate', (done) => {
// ... setup to return false
guard.canActivate(null, null).subscribe(x => {
// code to make
expect(x).toBe(false);
done();
});
});
Then add your other tests for canLoad to do the same. You may need more than one true/false scenario for each. You won't want to test the isAdmin
function directly as it isn't the public interface for this service.
CodePudding user response:
Thanks Wesley for your comment. Here's the solution
This is my code of guard
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot, UrlSegment, UrlTree } from '@angular/router';
import { first, map, Observable, tap } from 'rxjs';
import { GlobalState } from '../state/app.reducer';
import { Store } from '@ngrx/store';
import { RolesAccount } from 'src/app/pages/auth/interfaces/auth.constant';
@Injectable({
providedIn: 'root'
})
export class AdministratorGuard implements CanActivate, CanLoad {
constructor(private route: Router,private store: Store<GlobalState>) {}
canActivate(): Observable<true | UrlTree> {
return this.isAdmin()
}
canLoad(): Observable<true | UrlTree>{
return this.isAdmin()
}
isAdmin():Observable<true | UrlTree>{
return this.store.select('authentication').pipe(
first(), // take of first value
map(userStore => userStore?.userLogged?.role || ''),
//TODO we hardcode the email of administrator until role is in JWT
map(role => role === RolesAccount.ADMIN ? true : this.route.createUrlTree(['/home']))
);
}
}
And this is the test
import { TestBed } from '@angular/core/testing';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { lastValueFrom, of } from 'rxjs';
import { AuthComponent } from 'src/app/pages/auth/auth.component';
import { RolesAccount } from 'src/app/pages/auth/interfaces/auth.constant';
import { IUserState } from 'src/app/pages/auth/interfaces/auth.interfaces';
import { AdministratorGuard } from '../administrator.guard';
describe('AdministratorGuard', () => {
let guard: AdministratorGuard;
let store: MockStore<IUserState>;
let defaultState:IUserState = {
authentication:{
userLogged:{
name:'',
email:'',
phone:'',
accessToken:'',
refreshToken:'',
role: RolesAccount.USER
},
}
}
beforeEach(() => {
const routerStub = {
events: of('/'),
createUrlTree: (commands: any, navExtras = {}) => {}
};
TestBed.configureTestingModule({
imports:[
RouterTestingModule.withRoutes(
[
{
path: 'auth',
component: AuthComponent
},
]
),
],
providers:[
provideMockStore({
initialState:defaultState
}),
{ provide: Router, useValue: routerStub}
]
});
guard = TestBed.inject(AdministratorGuard);
store = TestBed.inject(MockStore);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
it('can Activate to be True ', () => {
const storeSpy = spyOn(store, 'select').and.callThrough();
guard.canActivate()
expect(storeSpy).toHaveBeenCalledTimes(1);
});
it('can canLoad to be True ', () => {
const storeSpy = spyOn(store, 'select').and.callThrough();
guard.canLoad()
expect(storeSpy).toHaveBeenCalledTimes(1);
})
it('validate role ADMIN',async () => {
const nextState:IUserState = {
authentication:{
userLogged:{
name:'Test',
email:'[email protected]',
phone:' 5411557788',
accessToken:'asfksakmfaskmfsakm',
refreshToken:'safla25l4235lllfs',
role: RolesAccount.ADMIN
},
}
}
store.setState(nextState);
const isAdmin = await lastValueFrom(guard.isAdmin())
expect(isAdmin).toBeTrue()
})
it('if is not admin,navigate home',async () => {
const nextState:IUserState = {
authentication:null
}
store.setState(nextState);
const isAdmin = await lastValueFrom(guard.isAdmin())
expect(isAdmin).toBeUndefined()
})
});