Anticipated request for spy navigation with path '/members' was expected, but unfortunately was not triggered

I am facing an issue with a service method that performs an HTTP delete operation. The expected behavior is that upon successful deletion, the page should be redirected to another location. However, during testing, I noticed that the router navigation function is not being called as expected. This resulted in a failure report generated by Jasmine:

Jasmine spec failure test:

Service: MemberDelete > Testing deleteMember() succesfully
Expected spy navigate to have been called with:
  [ [ '/members' ] ]
but it was never called.

The code snippet from my testing file member-delete.service.spec.ts is as follows:

import { HttpClient, HttpResponse} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed, async, inject } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { memberMockObject } from 'src/app/components/member/member.mocks';
import {createSpyFromClass, Spy} from 'jasmine-auto-spies';
import { MemberDeleteService } from './member-delete.service';
import { environment } from '../../../environments/environment';

describe('Service: MemberDelete', () => {
  let service: MemberDeleteService;
  let mockHttp: HttpClientTestingModule;
  let mockHttpTestingController: HttpTestingController;
  let router: Router;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports:[
        HttpClientTestingModule,
        RouterTestingModule
      ],
      providers:[
        { provider: MemberDeleteService, useValue: service },
        { provider: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    mockHttp = TestBed.inject(HttpClient);
    mockHttpTestingController = TestBed.inject(HttpTestingController);
    service = TestBed.inject(MemberDeleteService);  
    router = TestBed.inject(Router);
  });

  afterEach(() => {
    mockHttpTestingController.verify(); //Verifies that no requests are outstanding.
  });

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

  it('Testing deleteMember() succesfully', () => {
    spyOn(router, 'navigate');
    service.deleteMember(memberMockObject.fakeMember.id);
    expect(router.navigate).toHaveBeenCalledWith(['/members']);
  });

});

The relevant service method from member-delete.service.ts can be found below:

public deleteMember(memberId: number): void {
    this._http.delete(`${this._membersUrl}/${memberId}`).subscribe({
      complete: () => {
        this.router.navigate(
          [`/members`]
        );
      },
      error: (error) =>  {this.handleError(error);} 
    });
  }

Your assistance in resolving this issue would be greatly appreciated. Thank you for your help.

Answer №1

The problem lies in the absence of a response being passed for the delete method, causing it to skip the subscribe block.

To address this issue, make sure to follow the marked lines with !!:

import { HttpClient, HttpResponse} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed, async, inject } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { memberMockObject } from 'src/app/components/member/member.mocks';
import {createSpyFromClass, Spy} from 'jasmine-auto-spies';
import { MemberDeleteService } from './member-delete.service';
import { environment } from '../../../environments/environment';

describe('Service: MemberDelete', () => {
  let service: MemberDeleteService;
  let mockHttp: HttpClientTestingModule;
  let mockHttpTestingController: HttpTestingController;
  let router: Router;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports:[
        HttpClientTestingModule,
        RouterTestingModule
      ],
      providers:[
        // !! remove below line and provide actual service for testing
        // { provider: MemberDeleteService, useValue: service },
        MemberDeleteService,
        // !! remove below line - HttpClientTestingModule mocks the HttpClient for you
        // { provider: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    mockHttp = TestBed.inject(HttpClient);
    mockHttpTestingController = TestBed.inject(HttpTestingController);
    service = TestBed.inject(MemberDeleteService);  
    router = TestBed.inject(Router);
  });

  afterEach(() => {
    mockHttpTestingController.verify(); //Verifies that no requests are outstanding.
  });

  it('should ...', () => {
    expect(service).toBeTruthy();
  });
  
  // !! Use fakeAsync so we have better control of the subscribe
  it('Testing deleteMember() successfully', fakeAsync(() => {
    spyOn(router, 'navigate');
    service.deleteMember(memberMockObject.fakeMember.id);
    
    // !! Expect an HTTP request (find the pending request)
    const req = mockhttpTestingController.expectOne(request => request.url.includes(memberMockObject.fakeMember.id));
    // !! Ensure a delete method is used
    expect(req.request.method).toBe('Delete)';
    // !! Provide a response for the pending HTTP call
    req.flush({});
    // !! Wait for the subscribe to complete before assertion (tick handles this)
    tick();
    expect(router.navigate).toHaveBeenCalledWith(['/members']);
  }));

});

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 way to deactivate the click function in ngx-quill editor for angular when it is empty?

In the following ngx-quill editor, users can input text that will be displayed when a click button is pressed. However, there is an issue I am currently facing: I am able to click the button even if no text has been entered, and this behavior continues li ...

What is the best way to activate a click event on a dynamically generated input element with the type of 'file'?

I am facing a challenge in dynamically adding an input field of type 'file' to a form. The requirement is to allow the end user to add multiple files to the form, necessitating the use of an array concept. While I am able to inject dynamic elemen ...

Clicking on the prop in a React class component

This is the situation I am facing <List> {mainTags.map((mainTagItem) => { return ( <ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} button className={classes.mainTagItem}> <div className={classes. ...

The type inference in TypeScript sometimes struggles to accurately determine the type of an iterable

Struggling to get TypeScript to correctly infer the underlying iterable type for the function spread. The purpose of this function is to take an iterable of iterable types, infer the type of the underlying iterable, and return a new iterable with that infe ...

"Building a dynamic form with ReactJS, Redux Form, and Material UI - Implementing an

Currently working on a nested form framework that utilizes the redux form and material UI framework. The components have been developed up to this point - https://codesandbox.io/s/bold-sunset-uc4t5 My goal is to incorporate an autocomplete field into the ...

Encountering an issue when running the 'cypress open' command in Cypress

I am working with a Testing framework using node, cypress, mocha, mochawesome, and mochawesome-merge. You can check out the project on this github repo. https://i.sstatic.net/ItFpn.png In my package.json file, I have two scripts defined: "scripts": { ...

Leveraging Google maps to find nearby stores

I recently created a store locator but hit a roadblock when I discovered that Google Maps does not allow you to iframe the entire page. Is there a workaround for this issue to display the map? Or is there an alternative method that doesn't involve ifr ...

In a specific Angular project, the forget password feature is experiencing issues with sending email links in the Chrome browser. Interestingly, this issue only occurs the first

I'm currently working on an Angular 6 application that has been deployed with the domain "www.mydomain.com/". All the routes are functioning properly, such as "www.mydomain.com/dashboard" and "www.mydomain.com/products". However, there is an issue w ...

`How can you generate navigation paths using the ngRoute module?`

I am currently working on creating a basic navigation system like the one illustrated below: html: <html ng-app="myapp"> <body> <ul> <li><a href="pages/sub1">sub1</a></li> <li><a href="pages/ ...

What is the best way to ensure that an ASync function only continues once all necessary information has been collected?

retrieveStudentGrades() { let grades = {}; let totalStudents = this.state.studentDetails.length; let studentCount = 0; this.state.courses.map((course) => { this.state.studentDetails.map((student) => { request.get( ...

Optimal utilization of JSON in JavaScript API: Enhancing Performance, Reinforcing Maintainability, and Optimizing Resources

Currently, I am working on developing an application using Laravel and VueJS (along with Vuex). Although I do not have much experience in working with these frameworks or front-ends, I am curious to know the best practices for utilizing the data received f ...

Troubleshooting: Next.js - Issues with encodeURIComponent function when using `/` in getStaticPaths

Reproducible site showcasing the issue: Reproducible code example: https://github.com/saadq/nextjs-encoding-issue Homepage Food page The goal is to dynamically create static pages for different food items based on their titles. This functionality works ...

Trimming decimal points from large numbers using Javascript

Having trouble with a function that is supposed to format numbers in a more visually appealing way. It's glitchy - for example, 400 displays as 4H, which is correct. However, 430 displays as 4.3H, which is acceptable, but then 403 displays as 4.03H, w ...

For each array element that is pushed, create and return an empty object

I am encountering an issue with an array where the objects are being generated by a push operation within a function. Despite successfully viewing the objects directly in the array, when I attempt to use forEach to count how many times each id uses the ser ...

Google Maps integrated AWS server

While utilizing an AWS server for my application, I have encountered a difficulty with Google Maps. When running my application using localhost, Google Maps functions perfectly fine. However, when attempting to run the application using an IP address, it f ...

Exploring a React JS option for filtering repeated elements, as an alternative to Angular

While using Angular JS, I came across the 'filter' option within ng-repeat which is useful for live search. Here's an example: <div ng-repeat="std in stdData | filter:search"> <!-- Std Items go here. --> </div> &l ...

How to effectively merge DefaultTheme with styled-components in TypeScript?

I am facing an issue with integrating a module developed using styled-components that exports a theme. I want to merge this exported theme with the theme of my application. In my attempt in theme.ts, I have done the following: import { theme as idCheckThe ...

Is there a way to retrieve information from all pages of a table on a webpage using Chrome?

On a webpage, I have a table containing 100 rows that are displayed 20 at a time. By clicking "next page," I can view the following set of 20 rows and the table view is updated accordingly. I am able to access the data for each batch of 20 rows using dev ...

Troubleshooting base href issues in AngularJS routing

For a demonstration, I decided to try out this plunker from a tutorial that showcases tab routing across different pages. After downloading the entire zip file and running it as is (e.g. with all files in the same directory and utilizing CDN links), I enco ...

Creating a regular expression variable in Mongoose: A step-by-step guide

I am looking for a solution to incorporate a variable pattern in mongoose: router.get('/search/:name', async(req, res) => { name = req.params.name; const products = await Product.find({ name: /.*name*/i }).limit(10); res.send(prod ...