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

Merging objects with identical keys into a single object within an array using Typescript

Here is the array that I am working with: Arr = [{ code: "code1", id: "14", count: 24}, {code: "code1", id: "14", count: 37}] My objective is to consolidate this into a new array like so: Arr = [{ code: "code1& ...

The never-ending scroll feature in Vue.js

For the component of cards in my project, I am trying to implement infinite scrolling with 3 cards per row. Upon reaching the end of the page, I intend to make an API call for the next page and display the subsequent set of cards. Below is my implementatio ...

Is it possible to implement typed metaprogramming in TypeScript?

I am in the process of developing a function that takes multiple keys and values as input and should return an object with those keys and their corresponding values. The value types should match the ones provided when calling the function. Currently, the ...

What could be causing my vis.js network's node hover popups to not function properly?

I've encountered an issue where, despite adding the 'title' property to my node objects, the pop up window with the title content doesn't appear when I hover over a node. Here are the options I've chosen and how I've set up m ...

What is the correct way to specify the type in my functional component?

I was trying to define the type in my component in a different way, I know it can be done using classes but is there a way to achieve this with functional components without exporting the interface? Despite my extensive research, I couldn't find any r ...

Issue with Date generation in TypeScript class causing incorrect date output

I have a simple code snippet where I am creating a new Date object: var currentDate = new Date(); After running this code, the output value is: Sat May 11 2019 13:52:10 GMT-0400 (Eastern Daylight Time) {} ...

Combining types with additional features

Is it possible to configure the TypeScript compiler to generate an error when a function is called with an argument that can belong to both cases in a union type? For example: interface Name { name: string } interface Email { email: string } type ...

Is it feasible to amalgamate the states of several child components into a single parent component state in NextJS/React?

Issue I am faced with the challenge of handling multiple Child components that can pass their state to a Parent component. Now, I am looking to render several Parent components within a Grandparent component and merge the states of each Parent into a sing ...

Unable to perform the upgrade to Angular 6 due to an invalid range

I'm in the process of upgrading to Angular 6 As I follow the upgrade guide, I run into this issue: > ng update @angular/core Invalid range: ">=2.1.0" That's all the information I have. There are no additional warnings or ...

Angular breadcrumb component for creating a sidebar menu navigation

I am looking to enhance my sidebar menu with breadcrumb navigation. The menus currently include Menu1 and Menu2, each containing submenus such as subMenu1 and subMenu2. When a user clicks on Menu2, I want the breadcrumb trail to display as Home > Menu2 > ...

The concept of a generic type serving as a characteristic of an incoming argument

What is the best way to assign a type property of an argument to a generic in TypeScript? Here's the code snippet: const foo = <T = someObject.bar>(someObject: {[string]: any}): T => { return someObject.bar } How can we set the type of ...

Determine the class of an object within the "keyof" parameter by utilizing both property and generic types

I have a requirement to create an interface with generic types that can accept an object with keys representing "root field names" and values as arrays of objects defining sub-fields with the key as the name of the sub-field and the type as the value' ...

Encountering issues during the installation of node modules within an Angular application

I encountered an issue while setting up my angular app. Upon trying to install the node modules using the command npm i --save, I received the following error message. Error: npm ERR! code ENOTFOUND npm ERR! errno ENOTFOUND npm ERR! network This is a pr ...

Ways to retrieve a Class Level Variable within the onCellValueChanged function in agGrid

Exploring Angular with Typescript for the first time. I'm trying to access Class Level Variables within the onCellValueChanged event of agGrid, but encountering the following error: Cannot read property 'updateDict' of undefined Here&apo ...

The designated apiUser.json file could not be located within the _http.get() function

It's puzzling why my URL isn't working in _http.get('app/api/apiUsers') for Angular version 4.0.0, when it functions correctly in Angular version 2.3.1. The code is identical in both Angular versions: import { Injectable } from ' ...

How can we declare and showcase a generic object with an unspecified number and names of keys in React using TypeScript?

I am facing a challenge with objects that have a 'comments' field. While all the other fields in these different objects have the same types, the 'comment' field varies. I do not know the exact number or names of the keys that will be p ...

Can someone show me how to properly set up nested child routes in Angular 2?

My application structure is organized as shown below . ├── photos ├── posts ├── users │   ├── detail │   │   ├── address │   │   ├── family │   │   ├── information │   │   └ ...

How can I modify the pristine state of NgModel in Angular 2 using code?

Whenever you update the NgModel field, it will automatically set model.pristine to true. Submitting the form does not change the "pristine" status, which is expected behavior and not a bug. In my situation, I need to display validation errors when the fo ...

Integrating a fresh element into the carousel structure will automatically generate a new row within Angular

I'm currently working on an Angular4 application that features a carousel displaying products, their names, and prices. At the moment, there are 6 products organized into two rows of 3 each. The carousel includes buttons to navigate left or right to d ...

Guide on properly specifying mapDispatchToProps in a component's props interface

In my project, I have a connected component utilizing mapStateToProps and mapDispatchToProps along with the connect HOC from react-redux. My goal is to create concise and future-proof type definitions for this component. When it comes to defining types fo ...