Trying out the results of angular services

Seeking assistance in understanding the current situation:

I believe a simple tour of heroes app would be helpful in clarifying,

I am looking to set up some tests using Jest to verify if the behavior of a service remains consistent over time.

This is how the testing file is structured:

import { TestBed } from '@angular/core/testing';

import { HeroService } from './hero.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ServicesModule } from '@services/services.module';

describe('HeroService', () => {
  let httpMock: HttpTestingController;
  let service: HeroService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        ServicesModule,
        HttpClientTestingModule
      ],
    });
    httpMock = TestBed.inject(HttpTestingController);
    service = TestBed.inject(HeroService);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  // Fails due to timeout error ( spec #1 )
  it('getHeroes: should return a sorted list',  done => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(10);
      done();
    } );

    // Simulates the asynchronous passage of time

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

  });

  // Passes but does not properly check the value ( spec #2 )
  it('getHeroes: should return a sorted list', () => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(10);
    } );

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

  });

});

Error message for Spec 1 :

: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error

Spec 2 :

The test displays as green and passed, but the expect(heroes.length).toBe(10); is not thoroughly verified.

I have an InMemoryDbService with its DB configured like this (which should have a length of 4) causing the previous test to fail:

function getDbData() : Db{
  const heroes: any[] = [
    {
      id: 11,
      name: 'Maxwell Smart',
      saying: 'Missed it by that much.'
    },
    {
      id: 12,
      name: 'Bullwinkle J. Moose',
      saying: 'Watch me pull a rabbit out of a hat.'
    },
    {
      id: 13,
      name: 'Muhammad Ali',
      saying: 'Float like a butterfly, sting like a bee.'
    },
    {
      id: 14,
      name: 'Eleanor Roosevelt',
      saying: 'No one can make you feel inferior without your consent.'
    }
  ];

  return {heroes} as Db;
}

Imported like so in the main app module:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    // Core App Module
    CoreModule,
    // Routing Module
    AppRoutingModule,
    // Angular Specifics Module
    BrowserModule,
    HttpClientModule,
    // Development purpose Only
    HttpClientInMemoryWebApiModule.forRoot(
      InMemoryDataService, {
        dataEncapsulation: false,
        passThruUnknownUrl: true
      }
    ),
  ],
  providers: [ServicesModule],
  bootstrap: [AppComponent]
})

Additionally, Hero.service.ts for reference:

import { Injectable } from '@angular/core';
import { Hero } from '@models/hero.model';
import { HEROES } from '@services/in-memory-data/mock-heroes.service';


import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { MessageService } from '@services/messages/message.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ErrorHandlerService } from '@services/error-handler/error-handler.service';

// Marks the class as one that participates in the dependency injection system
// This method don't need a link in service.module
/*//
@Injectable({
  providedIn: 'root'
})
//*/

// This method need a link in service.module
@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // endpoint of the api service

  // TODO : HTTPInterceptor
  httpOptions = {
    headers: new HttpHeaders({'Content-Type': 'application/json'})
  };

  constructor(
    private http: HttpClient,
    private messageService: MessageService,
    private errorHandlerService: ErrorHandlerService) {
  }

  /** GET heroes from the server */
  getHeroes(): Observable<Hero[]> {
    return this.http.get<Hero[]>(this.heroesUrl)
      .pipe(
        tap(_ => this.messageService.add('fetched heroes')),
        catchError(this.errorHandlerService.handleError<Hero[]>('getHeroes', []))
      );
  }

  /** GET hero by id. Will 404 if id not found */
  getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url).pipe(
      tap(_ => this.messageService.add(`fetched hero id=${id}`)),
      catchError(this.errorHandlerService.handleError<Hero>(`getHero id=${id}`))
    );
  }

  /** PUT: update the hero on the server */
  updateHero(hero: Hero): Observable<any> {
    return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
      tap(_ => this.messageService.add(`updated hero id=${hero.id}`)),
      catchError(this.errorHandlerService.handleError<any>('updateHero'))
    );
  }

  /** POST: add a new hero to the server */
  addHero(hero: Hero): Observable<Hero> {
    return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
      tap((newHero: Hero) => this.messageService.add(`added hero w/ id=${newHero.id}`)),
      catchError(this.errorHandlerService.handleError<Hero>('addHero'))
    );
  }

  /** DELETE: delete the hero from the server */
  deleteHero(hero: Hero | number): Observable<Hero> {
    const id = typeof hero === 'number' ? hero : hero.id;
    const url = `${this.heroesUrl}/${id}`;

    return this.http.delete<Hero>(url, this.httpOptions).pipe(
      tap(_ => this.messageService.add(`deleted hero id=${id}`)),
      catchError(this.errorHandlerService.handleError<Hero>('deleteHero'))
    );
  }

  /* GET heroes whose name contains search term */
  searchHeroes(term: string): Observable<Hero[]> {
    if (!term.trim()) {
      // if not search term, return empty hero array.
      return of([]);
    }
    return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
      tap(x => x.length ?
        this.messageService.add(`found heroes matching "${term}"`) :
        this.messageService.add(`no heroes matching "${term}"`)),
      catchError(this.errorHandlerService.handleError<Hero[]>('searchHeroes', []))
    );
  }
}

If anyone has insights on what might be going wrong and how I could approach finding a solution, that would be greatly appreciated.

Thank you.

Answer №1

It is crucial to utilize the done callback in this scenario. One issue that needs to be addressed is setting the return value for the httpMock:

it('getHeroes: ensure a properly sorted list is returned',  done => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(4);
      done();
    } );

    // Simulating an asynchronous delay

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

    // Setting up the mock response for http.get
    req.flush([
    {
      id: 11,
      name: 'Maxwell Smart',
      saying: 'Missed it by that much.'
    },
    {
      id: 12,
      name: 'Bullwinkle J. Moose',
      saying: 'Watch me pull a rabbit out of a hat.'
    },
    {
      id: 13,
      name: 'Muhammad Ali',
      saying: 'Float like a butterfly, sting like a bee.'
    },
    {
      id: 14,
      name: 'Eleanor Roosevelt',
      saying: 'No one can make you feel inferior without your consent.'
    }
    ]);
    
    // Ensuring all calls have been completed
    httpMock.verify();

  });

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

Is there a possible solution to overcome the type error when utilizing dynamic environment variables in conjunction with TypeORM and TypeScripts?

I'm currently working on a backend project using the TsED framework, which leverages TypeScript and ExpressJS. To work with TypeORM, I have also integrated the dot-env package in order to utilize custom environment variables sourced from a .env file ...

The TypeScript error "Issue with Type Assertion: 'This expression is not callable Type'...' has no call signatures" occurs when there is a missing semicolon

Here's a simplified version of our original code: const start: number = 10 const end: number = 20 (someElement as HTMLInputElement).setSelectionRange(start, end) We encountered an error with the 20, where a red squiggly line appeared indicating ...

Angular utilizing a single pipe across various datasets

On my data page for products, I currently have a search pipe that works perfectly. Now, I also have another set of data called invoices. I want to use the same pipe to search through the invoices as well. Question How can I modify my pipe so that it can ...

Tips for accessing an item from a separate TypeScript document (knockout.js)

In the scenario where I need to utilize an object from another TypeScript file, specifically when I have an API response in one.ts that I want to use in two.ts. I attempted exporting and importing components but encountered difficulties. This code snippe ...

Tips for differentiating between elements with identical values in an HTML datalist using Angular

My boss is insisting that I use a datalist in our website interface to select an employee, even though there's no way to determine if the user typed in the name or picked from the list. The challenge is that the list must only display full names, but ...

Challenges arise when incorporating interfaces within a class structure

I created two interfaces outside of a class and then proceeded to implement them. However, when I tried to assign them to private properties of the class, something went wrong and I'm unable to pinpoint the issue. Can anyone offer assistance with thi ...

Resolve the problem in Angular 6 related to an unused type and the absence of a certain property

I recently watched a video tutorial (link: https://www.youtube.com/watch?v=z4JUm0Bq9AM) and encountered some errors in my CLI. The specific errors are as follows: ERROR in sidebar.component.ts(12,5): error TS7028: Unused label. sidebar.component.ts(14,56 ...

Combining Typescript interfaces to enhance the specificity of a property within an external library's interface

I have encountered a scenario where I am utilizing a function from an external library. This function returns an object with a property typed as number. However, based on my data analysis, I know that this property actually represents an union of 1 | 2. Ho ...

Challenges with CORS while Angular application is making a POST request to an ASP .Net API

I am currently working on developing an Angular application that will make HTTP requests to an ASP.NET API. Here is the request I am sending from Angular (hosted on http://localhost:4200): httpOptions = { headers: new HttpHeaders({ 'Conte ...

What steps should I take to instruct TypeScript to package a third-party library from the node_modules directory?

I am looking to configure the TypeScript Compiler in such a way that it utilizes node_modules/firebase/firebase.d.ts for typechecking my code, and also includes node_modules/firebase/firebase.js in the files where I import firebase functionalities. Althoug ...

"Exploring the world of asynchronous calls in Angular2

I'm encountering a common HTTP race condition, but I lack sufficient knowledge of Angular2 and Observables to troubleshoot my issue. Let me explain the setup: [ FormComponent ] | | 1| |6 | | [ MetadataService ...

Guide to implementing a universal style for a disabled mat-form-field in Angular 16

Is it possible to change the color of disabled mat-form-field from the global configuration in Angular Material 16 by defining it at the root level? Creating a custom material theme and custom material typography involves: @use "@angular/material&quo ...

Unspecified properties emerge post-Angular update

We recently consolidated multiple Angular 16 projects into one NX mono repository using Angular 17. Everything is functioning properly, EXCEPT we have noticed a peculiar change in behavior with our models. Previously, unset properties were simply not displ ...

How can I display a route from the starting point to the destination with all markers on a Leaflet map, using latitudes and longitudes from an API in Angular

Let's consider a scenario where we have a single bus with a fixed route, and all the bus stops along that route are marked. How can we achieve this in Angular using Leaflet maps as shown in the following image https://i.stack.imgur.com/GgEPS.png ...

How can we create a unique type in Typescript for a callback that is void of any return value?

When it comes to safe callbacks, the ideal scenario is for the function to return either undefined or nothing at all. Let's test this with the following scenarios: declare const fn1: (cb: () => void) => void; fn1(() => '123'); // ...

Issue: Failed to locate module @angular/core

When attempting to run an Angular 4 application, I consistently encounter the following error: ERROR in Could not resolve module @angular/core There are no additional errors present. There are no dependency issues whatsoever as @angular/core is located i ...

Navigating a relative path import to the correct `@types` in Typescript

When the browser runs, I require import * as d3 from '../lib/d3.js'; for d3 to be fetched correctly. I have confirmed that this does work as intended. However, the file that contains the above code, let's call it main.js, is generated from ...

Syntax for Angular services

I need help figuring out how to send specific data to the backend of my node.js and mysql application in order to trigger an email notification. Specifically, I want to pass two objects (account and labSwap) using a post method. What is the correct syntax ...

What could be the reason for my Angular 2 app initializing twice?

Can someone help me figure out why my app is running the AppComponent code twice? I have a total of 5 files: main.ts: import { bootstrap } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; impor ...

How to control the audio currentTime using Angular 2 component

Here is the HTML code snippet that creates an HTML5 audio element: <audio id ="audio2" controls="controls" autoplay="false" (canplay)="CanPlay($event)"> <source src="http://localhost:51657/Audio/1" type="audio/mp3"> </audio> In the ...