How to unit-test canActivate guard method of angular2 using Jasmine?

21,997

Solution 1

since no one answered my question, so I'm pasting my code snippet for the reference to help people who might get this situation.

sampleLoggedIn.guard.ts

import {Injectable} from '@angular/core';
import {Router, CanActivate} from '@angular/router';
import {StorageService} from '../storage.service';

@Injectable()
export class LoggedInGuard implements CanActivate {
    constructor(private router: Router, private storageService: StorageService) {
    }

    /**Overriding canActivate to guard routes
     *
     * This method returns true if the user is not logged in
     * @returns {boolean}
     */
    canActivate() {
        if (this.storageService.isLoggedIn) {
            return true;
        } else {
            this.router.navigate(['home']);
            return false;
        }
    }
}

sampleLoggedIn.guard.spec.ts

import {TestBed, async} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {CommonModule} from '@angular/common';
import 'rxjs/Rx';
import 'rxjs/add/observable/throw';
import {Router} from '@angular/router';
import 'rxjs/add/operator/map';
import {LoggedInGuard} from './loggedin.guard';
import {StorageService} from '../storage.service';
import {CookieService} from 'angular2-cookie/core';

describe('Logged in guard should', () => {
    let loggedInGuard: LoggedInGuard;
    let storageService: StorageService;
    let router = {
        navigate: jasmine.createSpy('navigate')
    };

    // async beforeEach
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [FormsModule, CommonModule, HttpModule],
            providers: [LoggedInGuard, StorageService, CookieService,
                {provide: Router, useValue: router}
            ]
        })
            .compileComponents(); // compile template and css
    }));

    // synchronous beforeEach
    beforeEach(() => {
        loggedInGuard = TestBed.get(LoggedInGuard);
        storageService = TestBed.get(StorageService);
    });

    it('be able to hit route when user is logged in', () => {
        storageService.isLoggedIn = true;
        expect(loggedInGuard.canActivate()).toBe(true);
    });

    it('not be able to hit route when user is not logged in', () => {
        storageService.isLoggedIn = false;
        expect(loggedInGuard.canActivate()).toBe(false);
    });
});

Solution 2

This question is pretty old - but as I was trying to find some detailed unit testing documentation myself right now, I just wanted to put my approach here. In general, if there are dependencies in my guard / service / component / whatever I think these should all be mocked and not the real services should be used. As theses services are not what we want to test in our unit test for the guard - we just want to test the guard. So here is a generic example how I would do it for a guard returning an observable:

import { MyGuard } from './path/to/your/guard';
import { TestBed } from '@angular/core/testing';
import { finalize } from 'rxjs/operators';

describe('MyGuard Test', () => {
    const createMockRoute = (id: string) => {
    return {
      params: { id: id }
    } as any;
  };

  const createMockRouteState = () => null;
  let guard: MyGuard;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        MyGuard,
      ]
    });

    guard = TestBed.get(MyGuard);
  });

  it('should not be able to activate invalid route', done => {
    const route = createMockRoute(null);
    const state = createMockRouteState();
    const res$ = guard.canActivate(route, state);
    res$.pipe(finalize(done)).subscribe(res => expect(res).toBeFalsy());
  });
});

and this is what I would do in your specific case (should work with angular 6, canActivate should also take 2 params):

import { LoggedInGuard } from './loggedin.guard';
import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { StorageService } from '../storage.service';

describe('LoggedInGuard', () => {
  let guard: LoggedInGuard;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        LoggedInGuard,
        { provide: Router, useClass: { navigate: () => null } },
        { provide: StorageService, useClass: { } }
      ]
    });

    guard = TestBed.get(LoggedInGuard);
  });

  it('should not be able to activate when logged out', () => {
    const storageService = TestBed.get(StorageService);
    storageService.isLoggedIn = false;
    const res = guard.canActivate(null, null);
    expect(res).toBeFalsy();
  });

  it('should be able to activate when logged in', () => {
    const storageService = TestBed.get(StorageService);
    storageService.isLoggedIn = true;
    const res = guard.canActivate(null, null);
    expect(res).toBeTruthy();
  });
});

Solution 3

If your Guard is asynchronous, it can be tested with asynchronous testing:

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { Observable, of } from 'rxjs';

describe('MyGuard', () => {
  let guard: MyGuard;
  let service: MyAsyncService;

  // async beforeEach
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
      ],
      providers: [
        MyGuard,
        MyAsyncService,
      ],
    });
  }));

  // synchronous beforeEach
  beforeEach(() => {
    guard = TestBed.inject(MyGuard);
    service = TestBed.inject(MyAsyncService);
  });

  it('should allow if service reports as allowed', (done) => {
    service.canFoo = (): Observable<boolean> => of(true);

    guard.canActivate(null, null).subscribe({
      next: (allowed: boolean) => {
        expect(allowed).toBeTrue();
        done();
      },
      error: err => {
        fail(err);
      },
    });
  });

  it('should reject if service reports as not allowed', () => {
    service.canFoo = (): Observable<boolean> => of(false);

    guard.canActivate(null, null).subscribe({
      next: (allowed: boolean) => {
        expect(allowed).toBeFalse();
        done();
      },
      error: err => {
        fail(err);
      },
    });
  });
});
Share:
21,997
Sumit Khanduri
Author by

Sumit Khanduri

Updated on March 18, 2021

Comments

  • Sumit Khanduri
    Sumit Khanduri over 3 years

    Sorry for asking this type of question. But I'm not able to find any blog or youtube tutorials on writing the canActivate guard file testing. Nor in the official documentation there is anything mentioned.

    any help will be much appreciated.