0

I would like to test if one of my helper functions are being called in the http service layer but I get a failed test. I am new to jest so please tell me know what am I doing wrong

Service Layer

public customerUpload(
    file: Blob,
    name?: string): Observable<CustomerResponse> {
    
    if (name!== '') {
      parameters = addQueryPara(name, 'name');
    }

return this.http.post(file, parameters)
)

I want to check if I called CustomerUpload with name, it should call addQueryPara

My Spec file Test


import * as helper from 'app/shared/helper.ts';


describe('customerService', () => {
  let service: customerService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
    });
    service = TestBed.inject(customerService);
  });

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


describe('when customer is called', () => {
  beforeEach(() => {
    const response = getMockResponse();
    jest.spyOn(service, 'customerUpload').mockReturnValue(of(response) as any);
  });

  it('should add http params', () => {
    service.customerUpload(new Blob(), 'learn');
    expect(jest.spyOn(helper, 'addQueryPara')).toHaveBeenCalledTimes(1); // This is failing
  });
});

});

addQueryPara is failing. How can I make sure if I pass a parameter, it calls addQueryPara?

Learn AspNet
  • 1,192
  • 3
  • 34
  • 74

3 Answers3

1

First of all, you should import the Service you are going to test, so you can inject it later. In this case, as you need to mock the HttpClient so it doesn't make any real request, you can do it with TestBed and HttpClientTestingModule (here is the official Angular documentation for jasmine, but it is similar).

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CustomerService } from './customer.service.ts'; // change this to the name of your route

describe('CustomerService', () => {
  let service: CustomerService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [CustomerService] // <- Here we import the real service
    });
    service = TestBed.inject(CustomerService);
  });

Then, in your test I see you try to mock the HTTP POST request. For that you can use HttpTestingController, that is part of HttpClientTestingModule (here you have a good tutorial on HTTP testing with Angular).

describe("when customerUpload is called", () => {
  const mockFile = // your mock
  afterEach(() => {
    // After every test, assert that there are no more pending requests.
    httpTestingController.verify();
  });
  it("should invoke HTTP POST with the file", () => {
    service.customerUpload(mockFile);

    // Check for correct requests: should have made one request to POST search from expected URL
    const req = httpTestingController.expectOne(customerUrl); // the url you expect to be called, including the queryParams, e.g.: customerUrl:4200?name=Mario
    expect(req.request.method).toEqual("POST");

    // Provide each http request with a mock response
    req.flush(getMockResponse()); 
  });
});

This is unit test theory: if the addQueryPara function is not exported outside of this service, you should not test it explicitly. Simply test that the result customerUpload works as you expect when name !== ''

Also, I'm not sure about that implementation for uploading a file, you should check out this comment about it.

adrisons
  • 3,443
  • 3
  • 32
  • 48
0

I don't know jest but I am thinking when you do jest.spyOn(...).mockReturnValue(...) you are stubbing the method and losing the implementation details with mockReturnValue.

Another thing is, you are spying AFTER calling the method but you should be spying before.

I would first use HttpClientTestingModule instead of the real HttpClientModule for testing so actual API calls are not sent and you can assert which API calls were sent.

Try removing the mockReturnValue and seeing if it passes.

import * as helper from 'app/shared/helper.ts';


describe('customerService', () => {
  let service: customerService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      // !! Change to HttpClientTestingModule
      imports: [HttpClientTestingModule],
    });
    service = TestBed.inject(customerService);
  });

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


describe('when customer is called', () => {
  beforeEach(() => {
    const response = getMockResponse();
    !! remove mockReturnValue
    jest.spyOn(service, 'customerUpload');
  });

  it('should add http params', () => {
    // !! move this up before calling the method so we are actually spying before
    const addSpy = jest.spyOn(helper, 'addQueryPara');
    service.customerUpload(new Blob(), 'learn');
    // !! assert now
    expect(addSpy).toHaveBeenCalledTimes(1);
  });
});

});

Check this link out for testing services with HTTP: https://testing-angular.com/testing-services/#testing-a-service-that-sends-http-requests

It uses Karma and Jasmine instead of Jest but the idea is the same.

AliF50
  • 16,947
  • 1
  • 21
  • 37
  • it still fails and says expected value 1 but actual 0 – Learn AspNet Jun 16 '22 at 12:46
  • Sorry, I am not sure then. – AliF50 Jun 16 '22 at 12:59
  • Hello, did you try jasmine-auto-spies ? Works very well to mock HttpClient calls. Here you can find examples : https://danielk.tech/home/how-to-test-angular-http-services. Here are the actual project with a Jest version : https://github.com/hirezio/auto-spies. Cheers – Alain Boudard Jun 17 '22 at 08:09
0

First you need to remove the following line:

jest.spyOn(service, 'customerUpload').mockReturnValue(of(response) as any);

As AliF50 mentioned, by doing this you are completely mocking the inner behavior of service.customerUpload therefore the actual implementation will never be called and it will always return of(response).

Additionally you might want to change your import for the HttpClientModule to HttpClientTestingModule in order to enable you mock and spy easily whenever you make any http request such as this.http.post;

This is how your code could look like (I assume addQueryPara is a typo but didn't change it for consistency with your sample):

import * as helper from 'app/shared/helper.ts';
import { customerService } from './customer-service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';


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

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

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

    describe('when customer is called', () => {
        it('should add http params', () => {
            // Given.
            const mockFile = new Blob();
            const mockName = 'learn';

            const addQueryParaSpy = jest.spyOn(helper, 'addQueryPara');

            // When.
            service.customerUpload(mockFile, mockName);

            // Then.
            expect(addQueryParaSpy).toHaveBeenCalledWith(mockName, 'name');
        });
    });
});