Mocking multiple services and their constructors in an Angular 2 TypeScript Jasmine test component

I've got this login component code snippet that I need help testing in Angular.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { BasicAuthService } from './../../services/auth/basic-auth.service';
import {ConfigService} from  '../../services/common/config.service';

import { RefdataService } from '../../services/common/refdata.service';
import { config } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { RouteGuardService } from '../../services/auth/route-guard.service';



@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  form: FormGroup;
  private formSubmitAttempt: boolean;
  invalidLogin = false;
  
  credentials:any;
  userPermissions: any;
  errorMessage: any = null;
  isLoading: boolean = false;
  
  

  constructor(
    private router: Router,
    private fb: FormBuilder,

    private basicAuthService: BasicAuthService,
    public config: ConfigService,

    private refdataService: RefdataService,
    private authGuard: RouteGuardService,

  ) { }

  ngOnInit(): void {

    this.form = this.fb.group({
      userName: ['', Validators.required],
      password: ['', Validators.required]
    });
  }


  handleAuthentication() {
    if (this.form.valid) {
      this.isLoading = true;
      console.log("handleAuthentication");

      
      let iUserName = this.form.value.userName;   
      let iPassword = this.form.value.password;
      
      this.credentials = {
        username: iUserName,
        password: iPassword
      }
      
      this.basicAuthService.getAuthentication(this.credentials)
          .pipe(
            switchMap(
              data => {
                console.log(data);
                this.invalidLogin = false;
                this.formSubmitAttempt = true;
                return this.refdataService.loadRefDataConfig();
              })
          ).subscribe(
            res => {             
                            
              // some variable updates
              this.router.navigate(['main']);

            },
            error => {
              this.isLoading = false;
              console.log(error.status);
              let mess = '';
              if (error.status === 401) {
                mess = 'Invalid Login credentials supplied! Correct the errors and re-submit.'
              } else {
                mess = error.error
              }
              this.errorMessage = { _body: 'Error Code: ' + error.status + '\nMessage: ' + mess };
              this.errorService.openSnackBar(this.errorMessage, 'Close', 'red-snackbar', 10000);
              
              this.invalidLogin = true;
              console.log(this.invalidLogin);
            }
          ); 
    }
  }

}

I want to write a test for this component and mock all services. Can someone assist me with this as I am new to Angular?

Below is my current failing test:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms'
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { LoginComponent } from './login.component';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;


  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        ReactiveFormsModule,
        FormsModule,
        HttpClientTestingModule
      ],
      declarations: [ LoginComponent ],

    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });


  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Error:

LoginComponent > should create
TypeError: Cannot read property 'environmentName' of undefined
    at <Jasmine>
    at new BasicAuthService (http://localhost:0000/_karma_webpack_/src/app/services/auth/basic-auth.service.ts:24:49)
    at Object.BasicAuthService_Factory [as factory] (ng:///BasicAuthService/ɵfac.js:5:10)
    at R3Injector.hydrate (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11248:1)
    at R3Injector.get (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11070:1)
    at NgModuleRef$1.get (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:24198:1)
    at Object.get (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:22101:1)
    at getOrCreateInjectable (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3921:1)
    at ɵɵdirectiveInject (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13752:1)
    at NodeInjectorFactory.LoginComponent_Factory [as factory] (ng:///LoginComponent/ɵfac.js:6:50)
    at getNodeInjectable (http://localhost:0000/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4029:1)
Expected undefined to be truthy.
Error: Expected undefined to be truthy.
    at <Jasmine>
    at UserContext.<anonymous> (http://localhost:0000/_karma_webpack_/src/app/components/login/login.component.spec.ts:35:23)
    at ZoneDelegate.invoke (http://localhost:0000/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
    at ProxyZoneSpec.onInvoke (http://localhost:0000/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)

The failure points to an issue with the constructor for the basic-auth-service.

export class BasicAuthService {
  
  environmentName = '';
  environmentUrl = '';

  constructor(
    private http: HttpClient,
    private config: ConfigService,
    private runtimeConfig: RuntimeConfigService,
  ) { 
    this.environmentName = runtimeConfig.config.environmentName;
    this.environmentUrl = this.environmentName == "localhost" ? "http://" +  runtimeConfig.config.serviceUrl : runtimeConfig.config.serviceUrl;    
  }

I need to know how to mock this service (and any other called services) to prevent its constructor from running. How can I create a simple mock of BasicAuthService for my component test?

Answer №1

Here are some helpful tips:

  • If you want to ignore dependencies, consider using @Optional. If you don't need to test a service, make it optional instead of injecting it and leaving it unused.

  • If something can't be made optional, try mocking it instead:

    { provide: UserService, useClass: UserServiceMock }
    
    export class UserServiceMock {}
    
  • Another approach is to use jasmine spyOn in Angular to mock dependencies and directly instantiate new components for more focused unit testing.

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

Error: Unable to call function onPopState from _platformLocation due to TypeError

After building an angular application, I encountered a strange issue where it runs smoothly in non-production mode but throws an error when running with --prod: Uncaught TypeError: this._platformLocation.onPopState is not a function I have double-checked ...

Having trouble getting the Next.js Custom Server to function properly with Firebase

I have deployed my Next.js App on Firebase and I am using a custom server (server.ts) to launch the app (node src/server.ts). The code for the server.ts file along with firebaseFunctions.js is provided below. The server.ts file works fine locally, which ...

FirebaseJS 4.0 is not compatible with projects created using Angular CLI

Having trouble integrating firebasejs 4.0 into my Angular CLI project. Here are the steps I followed: ng new firebasetest cd firebasetest ng serve ==> works fine After adding this line to index.html: <script src="https://www.gstatic.com/firebasej ...

Creating a Dynamic Table with Angular 6 - Automating the Population of Content Values

I have a task of populating a table with data from a JSON file. Take a look at the following code snippet: <table> <thead> <tr> <th *ngFor="let datakeys of listData[0] | keys">{{ datakeys }}</th> </tr> < ...

Challenges faced when integrating Angular with Bootstrap elements

I am currently in the process of developing a website using Angular 12, and although it may not be relevant, I am also incorporating SCSS into my project. One issue that I have encountered pertains to the bootstrap module, specifically with components such ...

What method can be used to seamlessly integrate Vue.js into a TypeScript file?

The focus here is on this particular file: import Vue from 'vue'; It's currently appearing in red within the IDE because the necessary steps to define 'vue' have not been completed yet. What is the best way to integrate without r ...

Encountered a 404 error while using a MEAN stack application

Encountering a 404 error while attempting to post data using Postman in Chrome. Any assistance will be greatly valued. I am following the application from this link: https://www.youtube.com/watch?v=wtIvu085uU0 Here is the code snippet: //importing modul ...

Issue with Typescript Application not navigating into the node_modules directory

After attempting to load the app from the root directory of our server, it became clear that this was not a practical solution due to the way our application uses pretty URLs. For instance, trying to access a page with a URL like http://www.website.com/mod ...

Tips for transferring a boolean value to a generic parameter in Java?

Looking to pass a boolean value to the Generic type in order to be utilized within a condition. This is the generic type interface OptionTypeBase { [key: string]: any; } type OptionsType<OptionType extends OptionTypeBase> = ReadonlyArray<Opt ...

What is the most efficient way to use map-reduce in TypeScript to filter a list based on the maximum value of an attribute?

Recently, I came across a list that looked something like this: let scores = [{name: "A", skills: 50, result: 80}, {name: "B", skills: 40, result: 90}, {name: "C", skills: 60, result: 60}, {name: "D", skills: 60, ...

The process of invoking the parent class's Symbol.iterator function from the child class's Symbol.iterator can be achieved by following a specific

I have two TypeScript classes defined below. class BaseIter { constructor(public a: number, public b: number, public c: number, public d: number){} *[Symbol.iterator](): Iterator<number> { yield this.a yield this.b yield this.c y ...

Incorporating an Angular Application into an Established MVC Project

I am working on an MVC project within an Area of a larger solution. My goal is to incorporate an Angular App into this area and integrate it with my MVC project. The catch is that this is not a .Net Core Mvc project. How can I configure my project to utili ...

An instance of an abstract class in DI, using Angular version 5

I have multiple components that require three services to be injected simultaneously with the same instance. After that, I need to create a new instance of my class for injecting the services repeatedly. My initial idea was to design an abstract class and ...

Strange Node.js: I always avoid utilizing `require()`, yet encountered an unexpected error

For more information on this particular issue, please refer to this link It's quite puzzling as I haven't used the require() function in my code, yet I'm receiving an error telling me not to use it. How odd! The problematic code snippet i ...

Set panning value back to default in Ionic

I need assistance with resetting the panning value. Essentially, I would like the panning value to return to 0 when it reaches -130. Below is my code snippet: swipeEvent($e) { if ($e.deltaX <= -130) { document.getElementById("button").click(); ...

Increasing the token size in the Metaplex Auction House CLI for selling items

Utilizing the Metaplex Auction House CLI (ah-cli) latest version (commit 472973f2437ecd9cd0e730254ecdbd1e8fbbd953 from May 27 12:54:11 2022) has posed a limitation where it only allows the use of --token-size 1 and does not permit the creation of auction s ...

Accessing images hosted on an IIS server from a client using a URL in an ASP .Net application

My application is built using Api ASP.Net, and I store the images in the "wwwroot" directory Content https://i.sstatic.net/JP2Lx.png After publishing my application, the folder structure remains intact and I need to be able to access the images through ...

Guide to simulating a scope in an Angular unit test using a directive instead of a controller

Recently, I have been working on understanding how to mock a $scope and controller using the $controller constructor. var scope = rootScope.$new(); it('should contain a testVar value at "test var home"', function(){ homeCtrl = $controller(&a ...

Discover additional information within extensive text by leveraging Angular 4 features

Looking for a solution to truncate text and limit it to 40 words, then display the full text when clicking on a "see more" link. I experimented with ng2-truncate, read-more-directive, and ng-text-truncate-2, but they were not compatible with Angular 4. ...

Exploring the testing capabilities of Angular JS in conjunction with third-party libraries such as

When testing an angular controller that utilizes an external library like Google Analytics event tracking, how can you approach it? For instance: $scope.showVolumn = function() { ga('send', { 'hitType': 'event', ...