How to test HttpService.Post calls using jest
Asked Answered
W

5

22

I am calling a API in nestjs service like 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));
    }
}

How can I mock/spyOn the call to this.httpClient.post() in jest to return response without hitting the actual API?

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

        expect(await myService.myMethod(input)).toBe(result);
    });
});
Weakling answered 22/1, 2020 at 13:28 Comment(0)
W
26

Got it working by using spyOn.

describe('myMethod', () => {
    it('should return the 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);
      });
  });
});
Weakling answered 22/1, 2020 at 15:59 Comment(3)
thanks! Do not forget to call done() as explained in https://mcmap.net/q/587713/-nest-js-issue-writing-jest-test-case-for-a-function-returning-observable-axios-responseKatey
This does only work if the httpService is exposed /public, which it in the most parts should not be. It would be better to directly mock it using a mock object or jests automock feature.Mintz
Instead of spyOn you can do httpService.post.mockReturnValue(axiosResponse);Nuisance
A
16

A good alternative to mocking the http service would also be to declare it in the providers array as follow.

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 providing your HttpService in the testing module rather than use spy on, you ensure that the HttpModule won't be imported or used and reduce your test code dependency to other services.

Adventuress answered 25/3, 2022 at 15:30 Comment(0)
P
3

As @Diaboloxx mentioned you should be mocking out your HttpService via the providers setup in your tests.

Ideally anything that you are dependency injecting into your constructor you should be providing and mocking out. That way your tests are isolated to the given file, and in this case to help prevent making real requests.

I like to use mockDeep from the library: jest-mock-extended to handle mocking on the providers to see what has been called on them, as well as to mock returned values. It's also nice because mockDeep will enforce type safety to validate that you're mocking valid return data.

Side Note: it is good practice to test that your dependencies are being called with what you expect.

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',
          },
        }),
      )
    })
  })
})
Phosgene answered 19/1, 2023 at 20:42 Comment(0)
J
2

I had a method that was using the result of my mocked post call, so I end up with this

describe('my test', 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);
  });
});

so by returning this "of()" you can even pipe the result of it

Journalese answered 11/10, 2022 at 7:49 Comment(0)
S
-1

If you are using axios

make sure in packakge.json add

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

Shuddering answered 13/2 at 4:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.