Issue
I have tried several times, but it seems like I cannot create a unit test for a very basic Guard in Angular 12 which has
- canActivate
- canActivateChild
as its main methods. Please find the following code:
@Injectable({
providedIn: 'root'
})
export class IsAuthenticatedGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.authService.getIsAuthenticated().pipe(
tap(isAuth => {
if (!isAuth) {
// Redirect to login
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(['/login']);
}
})
);
}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.canActivate(route, state);
}
}
The authService call inside the canActivate method shall return an Observable obtained by a BehaviourSubject object using the asObservable() call.
I have tried every possible test, but it seems like no comparison performed (toBe,toEqual, etc.) works for those two methods, nor is the spy on the navigation is triggered when the redirect is performed.
The following is a sample spec.ts class I created following some guides on the web:
function mockRouterState(url: string): RouterStateSnapshot {
return {
url
} as RouterStateSnapshot;
}
describe('IsAuthenticatedGuard', () => {
let guard: IsAuthenticatedGuard;
let authServiceStub: AuthService;
let routerSpy: jasmine.SpyObj<Router>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [SharedModule, RouterTestingModule]
});
authServiceStub = new AuthService();
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigate']);
guard = new IsAuthenticatedGuard(authServiceStub, routerSpy);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
const dummyRoute = {} as ActivatedRouteSnapshot;
const mockUrls = ['/', '/dtm', '/drt', '/reporting'];
describe('when the user is logged in', () => {
beforeEach(() => {
authServiceStub.setIsAuthenticated(true);
});
mockUrls.forEach(mockUrl => {
describe('and navigates to a guarded route configuration', () => {
it('grants route access', () => {
const canActivate = guard.canActivate(dummyRoute, mockRouterState(mockUrl));
expect(canActivate).toEqual(of(true));
});
it('grants child route access', () => {
const canActivateChild = guard.canActivateChild(dummyRoute, mockRouterState(mockUrl));
expect(canActivateChild).toEqual(of(true));
});
});
});
});
describe('when the user is logged out', () => {
beforeEach(() => {
authServiceStub.setIsAuthenticated(false);
});
mockUrls.forEach(mockUrl => {
describe('and navigates to a guarded route configuration', () => {
it('does not grant route access', () => {
const canActivate = guard.canActivate(dummyRoute, mockRouterState(mockUrl));
expect(canActivate).toEqual(of(false));
});
it('does not grant child route access', () => {
const canActivateChild = guard.canActivateChild(dummyRoute, mockRouterState(mockUrl));
expect(canActivateChild).toEqual(of(false));
});
it('navigates to the login page', () => {
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(routerSpy.navigate).toHaveBeenCalledWith(['/login'], jasmine.any(Object));
});
});
});
});
});
When I run the test file, I get something like this:
Expected object to have properties _subscribe: Function Expected object not to have properties source: Observable({ _isScalar: false, source: BehaviorSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownE rror: null, _value: false }) }) operator: MapOperator({ project: Function, thisArg: undefined }) Error: Expected object to have properties _subscribe: Function ...
Apparently, Karma expects a ScalarObservable of some sort, plus the navigation towards ['/login'] is not detected.
Would you mind giving me some advice on how to perform this test?
Thank you in advance.
Solution
Here is how I would configure TestBed module and test guard:
describe('IsAuthenticatedGuard', () => {
const mockRouter = {
navigate: jasmine.createSpy('navigate'),
};
const authService = jasmine.createSpyObj('AuthService', ['getIsAuthenticated']);
let guard: IsAuthenticatedGuard;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
providers: [
IsAuthenticatedGuard,
{ provide: Router, useValue: mockRouter },
{ provide: AuthService, useValue: authService },
],
}).compileComponents();
}),
);
beforeEach(() => {
guard = TestBed.inject(IsAuthenticatedGuard);
});
describe('when the user is logged in', () => {
beforeEach(() => {
authService.setIsAuthenticated.and.returnValue(of(true));
});
it('grants route access', () => {
guard.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).subscribe((result) => {
expect(result).toBeTrue();
});
});
it('grants child route access', () => {
guard.canActivateChild({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot).subscribe((result) => {
expect(result).toBeTrue();
});
});
});
});
Answered By - vitaliy kotov
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.