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

3

33

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.

Faery answered 19/12, 2016 at 8:15 Comment(1)
What exactly would you like to test? That the canActivate() method in your guard is indeed called or the code that's inside the canActivate() method?Soble
F
39

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);
    });
});
Faery answered 5/4, 2017 at 6:39 Comment(3)
canActivate() requires arguments. see this question for how to provide themTurki
You probably don't need a TestBed for that. Just instantiate your guard like a simple class with the Spies/Mocks necessary and call canActivate().Grosso
canActivate() requires inputDukey
M
5

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();
  });
});
Maghutte answered 5/10, 2018 at 11:25 Comment(1)
{ provide: Router, useClass: { navigate: () => null } } could cause TypeError: ctor is not a constructor error. Use useValue instead of useClass.Pilot
A
1

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);
      },
    });
  });
});
Anesthetize answered 17/3, 2021 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.