Tips for testing the canActivate guard method in Angular2 using Jasmine

Apologies for bringing up this particular inquiry. It seems that no blog or YouTube tutorials exist regarding testing the canActivate guard file. The official documentation also lacks any information on this topic.

Any assistance on this matter would be greatly valued.

Answer №1

Due to the lack of responses to my query, I am sharing my code snippet here for anyone facing a similar 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);
    });
});

Answer №2

While this question may be dated, I recently found myself in need of comprehensive unit testing documentation and decided to share my approach here. When it comes to testing guards, services, components, or any other dependencies, I believe in mocking them instead of using real services. The goal of unit tests for guards is to specifically test the guard itself, not the underlying services.

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());
    });
});

For your specific scenario, which should also work with Angular 6 and where canActivate takes 2 parameters:

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();
    });
});

Answer №3

If you want to test an asynchronous Guard, it can be done using asynchronous testing techniques:

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;

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

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

  it('should allow access if service indicates 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 access if service indicates 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);
      },
    });
  });
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The side-menu in Ionic Angular fails to appear when I attempt to access it by clicking on the icon

I am attempting to create a side-menu in Ionic Angular that can be used across multiple pages. To achieve this, I created a component called "menu-for-client", imported it in "Components.module.ts", and then imported the ComponentsModule in my pages. Howev ...

Issue with TypeScript not detecting exported Firebase Cloud Functions

Dealing With Firebase Cloud Functions Organization I am managing a large number of Firebase Cloud Functions, and in order to keep the code well-structured, I have divided them into separate files for each function category (such as userFunctions, adminFun ...

Issue with Nodemon and Typescript causing errors with Worker Threads

Currently, I am in the process of integrating a worker thread into my Typescript and node.js application. Let's take a look at my thread.ts file: import { Worker, isMainThread, parentPort, workerData } from "worker_threads"; let thread ...

Guide to altering the characteristics of a button

Here is the code for a button within My Template: <div *ngFor="let detail of details" class = "col-sm-12"> <div class="pic col-sm-1"> <img height="60" width="60" [src]='detail.image'> </div> <div ...

ES6 import of CSS file results in string output instead of object

Too long; Didn't read I'm facing an issue where the css file I import into a typescript module resolves to a string instead of an object. Can someone help me understand why this is happening? For Instance // preview.ts import test from './ ...

When attempting to create a new page in Ionic 5, the error message "**An NgModule could not be located**" is being displayed

While attempting to create a page using ionic g page pages/first, I encountered the following error: Could not find an NgModule. Use the skip-import option to skip importing in NgModule. [ERROR] Could not generate page However, when I tried ionic g page s ...

Having trouble displaying child nodes in MatTreeView with Angular 14?

In an Angular project, I am attempting to display a private group's data from GitLab (utilizing a public one for testing purposes). To achieve this, I have implemented the NestedTreeView component. While the parent nodes are displaying correctly, I am ...

Tips for ensuring that functions interpret the return value of (A | B)[] as equivalent to A[] | B[] in typescript

Upon closer examination, it appears that the types (A | B)[] and A[] | B[] are essentially interchangeable in TypeScript. The evidence for this can be seen in the following examples: var test1: number[] | string[] = [1] var test2: (number | string)[] = [" ...

Angular lazy loading routes do not function as intended

I currently have a project that is divided into different modules. Among these modules, the ones that are lazy loaded include the about and contact modules. The navigation menu containing the router links is located in a feature module alongside the header ...

Error encountered when making an HTTP request with NativeScript and Angular on Android version 4

Currently, I am in the process of developing an application using Angular NativeScript. Utilizing web services has been crucial for its functionality. However, I have encountered a challenge when making HTTP requests on Android 4, as it consistently throws ...

Optimized Image Loading with Angular17's ngSrc

Currently experimenting with Angular 17: // typescript get getWidth(): number { // this._el is an inject(ElementRef); const width = +this._el.nativeElement.querySelector( '[id="fs-img-container"]' ).clientWidth; ...

Assign Angular FromControl value to set the value of a select input

Seeking assistance on setting the initial value of the select dropdown below. I have attempted to do so through my formControl, but the value in the form is accurate while not reflecting in the view. HTML : <mat-form-field> <mat-select name="an ...

Is there a way to tally the checked mat-radio-buttons in Angular?

I am seeking a way to determine the number of radio buttons checked in a form so I can save that number and transfer it to a progress bar. <mat-radio-group name="clientID" [(ngModel)]="model.clientID"> <mat-radio-button *ngFor="let n of CONST ...

Is it feasible to tailor the structure of the new application directory?

Recently, I was assigned a task to explore customizing the folder structure for new apps, specifically Nest apps. After some research, I discovered that it is possible to create a "lib" folder within the "tools" directory and leverage patterns to automatic ...

What is the best way to organize class usage within other classes to prevent circular dependencies?

The engine class presented below utilizes two renderer classes that extend a base renderer class: import {RendererOne} from "./renderer-one"; import {RendererTwo} from "./renderer-two"; export class Engine { coordinates: number; randomProperty: ...

Is there a way to turn off tsc pretty printing using the configuration file?

My typescript program is intentionally broken but I want to fix it. 12:17:23:~/hello $ cat hello.ts console.log("Hello World" 12:17:29:~/hello $ cat package.json { "dependencies": { "typescript": "^5.2.2" ...

Are click events on nodes supported in Angular when using Mermaid.js?

I am currently working on integrating Mermaid flowchart drawing Javascript into my Angular 9 based client application. I have successfully created the flowchart diagram, but now I need to add a click event to a specific node with a function defined in Type ...

"Encountering a 404 (Not Found) error when attempting to access local backend APIs through Angular

Recently, I integrated Angular Universal into my project to enhance performance and improve SEO. Everything was working smoothly when I ran 'ng serve', with the app able to communicate with the backend. However, when I tried running 'npm run ...

Identifying Angular 2 templates post-file separation: a step-by-step guide

I am currently trying to figure out how to initiate a project in Angular 2 and have encountered an issue. Following the steps outlined in this Angular 2 guide, I was able to separate my .ts files from .js files by configuring my 'temp' directory ...

Displaying images dynamically in React from a source that is not public

There are 3 image options being imported, determined by the value in the state which dictates which of the 3 to display. import csv from '../../images/csv.svg'; import jpg from '../../images/jpg.svg'; import png from '../../images/ ...