A guide on testing HttpService.Post requests with jest

In my NestJS service, I am making a call to an API as shown below:

import { HttpService, Post } from '@nestjs/common';

export class MyService {

    constructor(private httpClient: HttpService) {}

    public myMethod(input: any) {
        return this.httpClient
            .post<any>(
                this.someUrl,
                this.createObject(input.code),
                { headers: this.createHeader() },
            )
            .pipe(map(response => response.data));
    }
}

I need to mock/spy on the call to this.httpClient.post() in jest so that it returns a response without actually hitting the API. How can I achieve this?

describe('myMethod', () => {
    it('should return the value', async () => {
        const input = {
            code: 'value',
        };
        const result = ['test'];
      
        // Need to use spyOn here?

        expect(await myService.myMethod(input)).toBe(result);
    });
});

Answer №1

Successfully resolved the issue using spyOn method.

describe('myMethod', () => {
    it('should successfully return the expected value', async () => {
      const input = {
        code: 'mock value',
      };

      const data = ['test'];

      const response: AxiosResponse<any> = {
        data,
        headers: {},
        config: { url: 'http://localhost:3000/mockUrl' },
        status: 200,
        statusText: 'OK',
      };

      jest
        .spyOn(httpService, 'post')
        .mockImplementationOnce(() => of(response));

      myService.myMethod(input).subscribe(res => {
        expect(res).toEqual(data);
      });
  });
});

Answer №2

Instead of mocking the http service, another effective approach would be to declare it in the providers array like this:

let httpClient: HttpService;
beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        {
          provide: HttpService,
          useValue: {
            post: jest.fn(() => of({
              // your response body goes here 
            })),
          },
        },
      ],
    }).compile();

    httpClient = module.get<HttpService>(HttpService);
  });

By simply providing your HttpService within the testing module instead of using spy on, you can ensure that the HttpModule is not imported or used, and reduce dependencies in your test code on other services.

Answer №3

As mentioned earlier by @Diaboloxx, it is important to mock out the HttpService in your tests using the providers setup.

For better test isolation and to prevent actual requests during testing, it's recommended to provide and mock out any dependencies that are being injected into your constructor.

I personally prefer using the mockDeep function from 'jest-mock-extended' library for mocking providers, tracking calls made on them, and specifying return values. It also helps ensure type safety when mocking return data.

Additional Tip: It's good practice to verify that your dependencies are called with the expected parameters.

import { HttpService } from '@nestjs/axios'
import { Test, TestingModule } from '@nestjs/testing'
import { mockDeep } from 'jest-mock-extended'
import { of } from 'rxjs'
import { AxiosResponse } from 'axios'
import { MyService } from '@/services/my.service'

describe('MyService', () => {
  let myService: MyService

  const httpService = mockDeep<HttpService>()

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [myService],
      providers: [
        {
          provide: HttpService,
          useValue: httpService,
        },
      ],
    }).compile()

    myService = app.get<MyService>(MyService)
  })

  describe('#myMethod', () => {
    const response: AxiosResponse<unknown, any> = {
      data: { hello: 'world' },
      headers: {},
      config: { url: 'http://localhost:3000/mockUrl' },
      status: 200,
      statusText: 'OK',
    }

    beforeEach(() => {
      httpService.post.mockReturnValue(of(response))
    })

    it('should return "Hello World!"', async () => {
      const result = await myService.myMethod({ code: 'value' })

      expect(result).toEqual({ hello: 'world' })
    })

    it('calls httpService.post with the correct params', async () => {
      await myService.myMethod({ code: 'value' })

      expect(httpService.post).toHaveBeenLastCalledWith(
        'someBaseUrl/param1/param2',
        { body: 'body' },
        expect.objectContaining({
          headers: {
            header: 'header',
          },
        }),
      )
    })
  })
})

Answer №4

During my testing process, I came across a scenario where I needed to utilize the output of a mocked post call. This led me to modify my approach as shown below:

describe('unit test for service', function () {
  let service: LegalTextAdminClientFactory;

  const httpService = {
    get: jest.fn(),
    post: jest.fn().mockImplementation(() => of({ data: {} })),
  };
  const configService = {
    get: jest.fn().mockReturnValue('mock'),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        { provide: HttpService, useValue: httpService },
        { provide: ConfigService, useValue: configService },
        LegalTextAdminClientFactory,
      ],
    }).compile();

    service = await module.get(LegalTextAdminClientFactory);
  });
});

By using the "of()" method in the mock implementation, it allowed me to further manipulate the result if needed.

Answer №5

If you happen to be utilizing axios library

it is essential to update your packakge.json file with the following:

"moduleNameMapper": {
      "^@shared/shared(|/.*)$": "<rootDir>/libs/shared/src/$1",
      "^@app/shared(|/.*)$": "<rootDir>/libs/shared/src/$1",
      "^axios$": "<rootDir>/node_modules/axios/dist/axios.min.js"
}

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 in TypeScript: The property 'data' is not found within type '{ children?: ReactNode; }'. (ts2339)

Question I am currently working on a project using BlitzJS. While fetching some data, I encountered a Typescript issue that says: Property 'data' does not exist on type '{ children?: ReactNode; }'.ts(2339) import { BlitzPage } from &q ...

Guide on transforming a JSON string into an array of custom objects using the json2typescript NPM module within a TypeScript environment

I am looking to utilize the json2typescript NPM module to convert a JSON string into an array of custom objects. Below is the code I have written. export class CustomObject { constructor(private property1: string, private property2: string, private p ...

Converting a C# Dictionary<string,string> to a TypeScript Map<string,string>

Struggling to find the best approach for handling key:value pairs in TypeScript when receiving a dictionary object from the C# backend. Everything attempted so far has not yielded the expected results. This is the C# code snippet: var displayFields = ...

Executing NPM scripts in bin directory upon import

My TypeScript 4.8.4 library is being packaged with npm. Within my package.json, I have a "bin" section that includes several utility scripts for consumers of the package. "bin": { "generate-docs": "./docs/docs.py&qu ...

What is the best way to incorporate cors modules in TypeScript?

I am encountering some difficulties while attempting to import the cors module in a TypeScript project using Express. When I use the following code: import cors from "cors"; I receive the following error message: "Cannot find module &apos ...

The OnPrepareResponse method in StaticFileOptions does not trigger when serving the index.html file

Currently, I am attempting to disable caching for index.html in my Angular SPA that is connected to a .NET Core 2.2 backend. I am following the instructions provided in this particular answer by implementing an OnPrepareResponse action for my StaticFileOp ...

Error: The parameter should be a string, not an object

I am having trouble with a function that is supposed to return a string but instead, it returns an object. Unfortunately, I cannot use the .toString() method either. currentEnvironment: string = "beta"; serverURL: string = this.setServerURL(this.currentEnv ...

Only one argument is accepted by the constructor of NGRX data EntityServicesBase

In an attempt to enhance the convenience of my application class, I decided to create a Sub-class EntityServices based on the NGRX/data documentation which can be found here. Despite following the provided example, it appears that it does not function pro ...

Running terminal commands in typescript

Can Typescript be used to run commands similar to JavaScript using the Shelljs Library? ...

I encountered an issue while trying to implement a custom pipe using the built-in pipe

My custom pipe seems to be functioning well, except for the built-in pipes not working within it. I've imported Pipe and can't figure out what could be causing the issue. Below is the code with the errors: import { Pipe, PipeTransform } from &a ...

Is it possible to dynamically assign a CSS class to a DOM element during a drag operation while the mouse button is held down?

I am currently working on developing a unique pathfinder visualizer. My progress so far includes creating a grid of size 16x45 using the function below: export const drawBoard = () => { const boardContainer: HTMLDivElement | null = document.querySelec ...

When the key property is utilized, the state in react useState is automatically updated; however, for updating without it, the useEffect or a

I am working on a component that looks like this: import React, { useState } from "react"; import { FormControl, TextField } from "@material-ui/core"; interface IProps { text?: string; id: number; onValueChange: (text: stri ...

What is the best way to determine the final letter of a column in a Google Sheet, starting from the first letter and using a set of

My current approach involves generating a single letter, but my code breaks if there is a large amount of data and it exceeds column Z. Here is the working code that will produce a, d: const countData = [1, 2, 3, 4].length; const initialLetter = 'A&a ...

Guide on transferring data from one table to another by choosing one or multiple rows with the help of Angular 2

https://i.sstatic.net/CF1Xo.pngHello, I'm currently facing an issue with my requirements. I'm attempting to display two tables. Data for Table One: TableOne =[ { "Id": 1, "Name": "ONLINE", "Status": false, "Track": false, }, ...

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 ...

Is it possible to use TypeScript in a React Native project with a JavaScript file?

Currently, I am learning React Native by working on app clones like Instagram and YouTube. I have recently started an AirBnb clone project, but I'm facing some issues with the initial build. One issue I noticed is that in 'App.js', the temp ...

Developing bespoke styles in Angular Material 2

I am in the process of developing a unique theme for my Angular 2 application, incorporating various components from angular material 2. Despite searching extensively online, I haven't been able to find much relevant information. The only documentati ...

Having Trouble Displaying Material UI Icons in Your React App? Here's Why: "Invalid Objects as React Children"

I have been working on a basic app that showcases information about a patient. In this specific component, I am only displaying the name, occupation, and a symbol from Material UI to indicate whether the patient is male or female. However, when I attempt ...

The application within the Main Module is not being acknowledged by the other components within the module

I am facing an issue with my AngularJS application where the directive I created within the 'FormTest' module is not recognizing the variable 'app' even though it is defined within the same module. The error message I receive is TS2304 ...

Modifying the text of a material UI button depending on a particular condition

I have a component that uses the Material UI button, and I need to change the text of the button based on a condition. If the order amount is 0, I want it to display "cancel", otherwise, it should show "refund". Can someone guide me on how to achieve thi ...