Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest: How to mock a promise on the same file for resolve and reject options?

So I am attempting to test the positive and negative sides of my implementation. If I do this:

jest.mock('./apiClient', () => ({
  get: jest.fn((url: string) => Promise.resolve({ data: mockData }))
}));

jest.mock('./apiClient', () => ({
  get: jest.fn().mockImplementation((url: string) => {
    console.log('error result');
    return Promise.reject(mockError);
  })
}));

Then the only test passing will be the last one declared. In the above case, it will be the reject case which will pass and the first mock will be ignored.

This is my whole test:

// @ts-ignore
import ApiClient from './apiClient';
import ApiService from './apiService';

const mockData = {};
const mockError = { message: 'Smth Bad Happened' };

const firstCallback = jest.fn((data: any) => data);
const secondCallback = jest.fn((data: any) => data);

jest.mock('./apiClient', () => ({
  get: jest.fn((url: string) => Promise.resolve({ data: mockData }))
}));

jest.mock('./apiClient', () => ({
  get: jest.fn().mockImplementation((url: string) => {
    console.log('error result');
    return Promise.reject(mockError);
  })
}));

describe('apiService', () => {
  it('should call callbacks consequently', done => {
    ApiService.makeApiCall('testUrl', firstCallback, secondCallback).then(() => {
      expect(firstCallback).toBeCalledTimes(1);
      expect(firstCallback).toBeCalledWith(mockData);

      expect(secondCallback).toBeCalledTimes(1);
      expect(secondCallback).toBeCalledWith(firstCallback(mockData));
      done();
    });
  });

  it('should handle error', done => {
    console.error = jest.fn();
    ApiService.makeApiCall('testUrl', firstCallback, secondCallback).then(() => {
      expect(firstCallback).toBeCalledTimes(0);
      expect(secondCallback).toBeCalledTimes(0);
      expect(console.error).toBeCalledTimes(1);
      expect(console.error).toBeCalledWith('ApiClient testUrl', mockError);
      done();
    });
  });
});

As it is right now, the test passing is should handle error which is the second one, but if I have switch the positions from

jest.mock('./apiClient', () => ({
  get: jest.fn((url: string) => Promise.resolve({ data: mockData }))
}));

jest.mock('./apiClient', () => ({
  get: jest.fn().mockImplementation((url: string) => {
    console.log('error result');
    return Promise.reject(mockError);
  })
}));

To

jest.mock('./apiClient', () => ({
  get: jest.fn().mockImplementation((url: string) => {
    console.log('error result');
    return Promise.reject(mockError);
  })
}));

jest.mock('./apiClient', () => ({
  get: jest.fn((url: string) => Promise.resolve({ data: mockData }))
}));

then the test passing will be should call callbacks consequently, so what can I do to mock both reject and resolve without interfering one with the other?

like image 709
Non Avatar asked Mar 04 '23 12:03

Non


1 Answers

I've reached this question while I was looking for a good practice to do that, because I had the same problem but I found a workaround to make it work. And although I suppose you already solved this issue, I'll leave here my temporary solution for future readers.

Basically I overridden the mock implementation of the method right in the test where I want to reject the promise.

So I would get rid of the reject implementation before the describe declaration and add it in the 'should handle error' test by this way:

ApiClient.get: jest.fn().mockImplementation((url: string) => {
    console.log('error result');
    return Promise.reject(mockError);
})

Your final test would look like this:

// @ts-ignore
import ApiClient from './apiClient';
import ApiService from './apiService';

const mockData = {};
const mockError = { message: 'Smth Bad Happened' };

const firstCallback = jest.fn((data: any) => data);
const secondCallback = jest.fn((data: any) => data);

jest.mock('./apiClient', () => ({
  get: jest.fn((url: string) => Promise.resolve({ data: mockData }))
}));

describe('apiService', () => {
  it('should call callbacks consequently', done => {
    ApiService.makeApiCall('testUrl', firstCallback, secondCallback).then(() => {
      expect(firstCallback).toBeCalledTimes(1);
      expect(firstCallback).toBeCalledWith(mockData);

      expect(secondCallback).toBeCalledTimes(1);
      expect(secondCallback).toBeCalledWith(firstCallback(mockData));
      done();
    });
  });

  it('should handle error', done => {
    ApiClient.get: jest.fn().mockImplementation((url: string) => {
        console.log('error result');
        return Promise.reject(mockError);
    });
    console.error = jest.fn();
    ApiService.makeApiCall('testUrl', firstCallback, secondCallback).then(() => {
      expect(firstCallback).toBeCalledTimes(0);
      expect(secondCallback).toBeCalledTimes(0);
      expect(console.error).toBeCalledTimes(1);
      expect(console.error).toBeCalledWith('ApiClient testUrl', mockError);
      done();
    });
  });
});

I think this is not the best way to do it, but it is not bad at all, and it should work for this example.

I'll keep looking for a smarter solution.

like image 129
christiansr85 Avatar answered Apr 28 '23 03:04

christiansr85