knee-cola / jest-mock-axios

Axios mock for Jest
252 stars 42 forks source link

No request to respond to! when used along with multiple async await #46

Open AnandShiva opened 4 years ago

AnandShiva commented 4 years ago

Thanks for your package. Love it ! But I seem to have an issue with it when using it along with async-await functions. Test Code looks like test("authenticate", async () => { let promiseObj = authenticate(email, password).then((msg) => {}).catch(catchFn) // simulating a server response let responseObj = { data: ["centralpet"] }; mockAxios.mockResponse(responseObj); expect(catchFn).not.toHaveBeenCalled(); return promiseObj; })

Now my source code - authenticate implementation looks like

authenticate = async (email, password) => { //This called a chrome async method. Not axios. let client = await utils.updateClientDetailsFromEmail(email); // This calls the axios methods. let status = await httpService.makeNetworkCall(requestObject); return status; }

Since inside the authenticate I am using await, the function gets paused before calling axios. Meanwhile, the calling function continues execution and goes to mockResponse. There mockAxios finds that no axios functions are in Queue so it throws No request to respond to! error. If I await the authenticate call the mockResponse never gets executed so axios times out.

Can you check this issue or is there any workaround or config I am missing to avoid queuing of axios calls?

kingjan1999 commented 4 years ago

This looks like a duplicate of #44. Currently, I am not aware of any workaround for this, except for splitting up the two await calls in two different functions. But I will look at this again soon

AnandShiva commented 4 years ago

@kingjan1999 Any reason why mockResponse should be set after the axios call has been made? If it is possible to change this architecture I can fork a branch and have this mocking response after the call to be a configuration parameter.

kingjan1999 commented 4 years ago

This is currently due to how this library works: It takes any requests made, puts them into a queue and then pops the first one (or the specified one) when mockResponse is called. Feel free to make a pull request to make this more flexible

Martin7mind commented 4 years ago

@AnandShiva did you find a workaround?

BrendanBall commented 4 years ago

Why can't this work like a regular jest mock where you're able to for example do

  test('foo', async () => {
    const mock = jest.fn()
    mock
      .mockResolvedValueOnce({ foo: 'bar' })
      .mockResolvedValueOnce({ foo: 'bar' })
      .mockRejectedValueOnce(new Error('oh no'))
    await expect(mock()).resolves.toEqual({ foo: 'bar' })
    await expect(mock()).resolves.toEqual({ foo: 'bar' })
    await expect(mock()).rejects.toThrowError(new Error('oh no'))
  })

Jest fully supports async tests and seen as this package is specifically written for jest I don't see a reason to work outside of its framework.

pedrotorchio commented 4 years ago

Why can't this work like a regular jest mock where you're able to for example do

  test('foo', async () => {
    const mock = jest.fn()
    mock
      .mockResolvedValueOnce({ foo: 'bar' })
      .mockResolvedValueOnce({ foo: 'bar' })
      .mockRejectedValueOnce(new Error('oh no'))
    await expect(mock()).resolves.toEqual({ foo: 'bar' })
    await expect(mock()).resolves.toEqual({ foo: 'bar' })
    await expect(mock()).rejects.toThrowError(new Error('oh no'))
  })

Jest fully supports async tests and seen as this package is specifically written for jest I don't see a reason to work outside of its framework.

this' been a pain for me too

aarlaud commented 4 years ago

I get the same "No request to respond to!" after 10 successful responses using await. My first 10 test cases (with one await call in each) pass well and then this fails. Tried to change the code but always seems to be a 10 limit somehow. Not clear what seems to cause this as I could not see something like a limit in the queue size. Any ideas? Nevermind me :facepalm:

gnarea commented 4 years ago

In case it helps anyone else running into this and/or #44: Try axios-mock-adapter

ghill-legwork commented 3 years ago

I worked around this by doing something like:

    it('can pass through all filter params to the API', async (): Promise<void> => {
      mockAxios.get.mockImplementationOnce(() => {
        return Promise.resolve(resultArray);
      });
      const result = await getResource(
        {}, // query string params
      );
      const requestConfig = mockAxios.get.mock.calls[0][1]; // First call, second arg
      const requestUrl = mockAxios.get.mock.calls[0][0]; // First call, second arg
      expect(requestUrl).toEqual('/example/resource/endpoint');
      expect(requestConfig.params).toEqual({});
      expect(result).toEqual(resultArray);
    });

Excuse the pulling out of the mock call args, I had to only test a subset of them but you get the general idea.

douglascayers commented 1 year ago

Came here with the same struggle.

My solution was to mock both axios and got using jest's out-of-the-box mocking framework. To clarify, you don't have to use both http packages, I'm just showing that the same mocking concept can be applied to either you're using.

For this example, I'm using the following versions:

import axios, { AxiosStatic } from 'axios';
import got, { Got } from 'got';

jest.mock('axios');
jest.mock('got');

describe('mock-test', () => {
  let mockedAxios: jest.MaybeMocked<AxiosStatic>;
  let mockedGot: jest.MaybeMocked<Got>;

  beforeEach(() => {
    mockedAxios = jest.mocked(axios);
    mockedGot = jest.mocked(got);
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  test('mocked post', async () => {
    const mockResponse = {
      ok: true,
      result: {
        id: 42,
        name: 'Doug'
      },
    };

    mockedAxios.post = jest.fn().mockReturnValue(mockResponse);

    const response = await client.createUser({ name: 'Doug' });

    expect(mockedAxios.post).toHaveBeenCalledWith(
      expect.stringContaining('/user'),
      {
        name: 'Doug'
      }
    );

    expect(response).toBe(mockResponse.result);
  });

  test('mocked error', async () => {
    mockedGot.get = jest.fn().mockRejectedValue(new Error('failure'));

    await expect(async () => {
      await client.getUserById(42);
    }).rejects.toThrow('failure');

    expect(mockedGot.get).toHaveBeenCalledWith(
      expect.stringContaining('/user/42'),
    );
  });
});