knee-cola / jest-mock-axios

Axios mock for Jest
252 stars 42 forks source link

jest-mock-axios incompatible with CRA 4 / resetMocks: true #63

Open alyssaruth opened 3 years ago

alyssaruth commented 3 years ago

Hi!

We're using this library in a project built with Create React App, and have followed the async / await testing pattern outlined in https://github.com/knee-cola/jest-mock-axios/issues/28.

I'm trying to upgrade us to CRA 4, but doing so is breaking all of these tests with Error: No request to respond to!. Bumping major CRA version bumps a load of sub-dependencies, so guessing one of these is the actual cause (jest seems most likely) - I'm happy to check other package versions before/after the upgrade if there are any in particular you think will be relevant!

We were using version 2.3.0 of this library, but can also reproduce the issue on the latest version (4.2.1). I've put together a SRE of three files to demonstrate what's happening.

Our axios instance, defined in axiosInstance.ts

import axios from 'axios'

export const axiosInstance = axios.create({
  baseURL: '/api',
  timeout: 20000,
  withCredentials: true,
  headers: { 'X-Csrf-Prevention': 'true' },
})

A fake API to test in sreApi.ts

import { AxiosResponse } from 'axios'
import { axiosInstance } from './axiosInstance'

export const callEndpoint = async (data: string): Promise<AxiosResponse> => {
  return axiosInstance.post('/endpoint', data)
}

The test that fails after the upgrade, but passes before in sreApi.test.ts

import { callEndpoint } from './sreApi'
import mockAxios from 'jest-mock-axios'

it('should call the correct endpoint', async () => {
  const promise = callEndpoint('some-data')
  mockAxios.mockResponse({ data: 'response' }) <-- blows up here
  const result = await promise

  expect(mockAxios.post).toHaveBeenCalledWith('/endpoint', 'some-data')
  expect(result.data).toBe('response')
})

In the above example, the mockResponse line blows up with:

Error: No request to respond to!
    at Function.mockResponse (/home/me/sre-package/node_modules/jest-mock-axios/lib/mock-axios.ts:170:15)
    at Object.<anonymous> (/home/me/sre-package/src/sre/sreApi.test.ts:6:13)
    at Promise.then.completed (/home/me/sre-package/node_modules/jest-circus/build/utils.js:276:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (/home/me/sre-package/node_modules/jest-circus/build/utils.js:216:10)
    at _callCircusTest (/home/me/sre-package/node_modules/jest-circus/build/run.js:212:40)
    at processTicksAndRejections (internal/process/task_queues.js:85:5)
    at _runTest (/home/me/sre-package/node_modules/jest-circus/build/run.js:149:3)
    at _runTestsForDescribeBlock (/home/me/sre-package/node_modules/jest-circus/build/run.js:63:9)
    at run (/home/me/sre-package/node_modules/jest-circus/build/run.js:25:3)
    at runAndTransformResultsToJestFormat (/home/me/sre-package/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:176:21)

Interestingly, reordering the test like this still fails but does pass the call assertion, so mockAxios.post is definitely still being called:

it('should call the correct endpoint', async () => {
  const promise = callEndpoint('some-data')
  expect(mockAxios.post).toHaveBeenCalledWith('/endpoint', 'some-data')
  mockAxios.mockResponse({ data: 'response' }) <-- still blows up here
  const result = await promise

  expect(result.data).toBe('response')
})

Versions that work:

jest: 24.9.0 react-scripts: 3.4.4 axios: 0.18.1 jest-mock-axios: 4.2.1

Versions that don't work (after upgrade):

jest: 26.6.0 react-scripts: 4.0.1 axios: 0.18.1 jest-mock-axios: 4.2.1

kingjan1999 commented 3 years ago

Hi,

thanks for reporting - I was able to reproduce this with the latest version of create-react-app / react-scripts. It looks like this is related to react-scripts defaulting jest's resetMocks to true since v4. Consequently you can workaround this bug by adding:

  "jest": {
    "resetMocks": false
  }

in your package.json while I am further investigating this issue.

alyssaruth commented 3 years ago

Thanks for looking into this and responding so quickly with a workaround! :tada:

Chris-Thompson-bnsf commented 3 years ago

I also ran into this issue after the CRA 4.0 upgrade, and I can confirm that the resetMocks: false workaround worked for our project as well.

alyssaruth commented 3 years ago

Hmm, the resetMocks: false workaround doesn't seem to be working here - I'm still seeing Error: No request to respond to!

kingjan1999 commented 3 years ago

Hi,

there are several upstream issues for the resetMocks behavior breaking existing test behavior (e.g. https://github.com/facebook/create-react-app/issues/9935). I'm still looking for a feasible solution except for the aforementioned workaround.

@alexburlton-sonocent I created a repro-repo using the example code you provided at kingjan1999/jest-mock-axios-cra-repo. Can you try if this works for you and look for differences between this repo and your project?

alyssaruth commented 3 years ago

Sorry for going AWOL, the create-react-app upgrade dropped off our radar for a bit so I've not been looking at this for a while.

Dug into this a bit more today and have concluded that it's our use of react-app-rewired which is breaking the workaround. If I change our test command to go via vanilla react-scripts rather than react-app-rewired, I can indeed see that specifying the resetMocks option fixes the problem.

So now I need to figure out if we can drop react-app-rewired for running tests, or failing that how to make it respect the resetMocks option. Have you made any progress on a feasible solution for making these tests work with resetMocks set to true?

doutatsu commented 2 years ago

Not sure if related, but I am getting Cannot set properties of undefined (setting 'Authorization') after upgrading to Jest 28. This comes from my custom axios configuration that sets the auth token:

const setAuthConfig = (config) => {
  const token = localStorage.getItem('access');

  if (token) { config.headers.Authorization = `Bearer ${token}`; }

  return config;
};
    TypeError: Cannot set properties of undefined (setting 'Authorization')

      18 |   const token = localStorage.getItem('access');
      19 |
    > 20 |   if (token) { config.headers.Authorization = `Bearer ${token}`; }
         |                ^
      21 |
      22 |   return config;
      23 | };

Setting resetMocks: false doesn't help with this issue for me though

kingjan1999 commented 2 years ago

@doutatsu Can you post a more complete example that shows how you invoke setAuthConfig / axios? (maybe in a separate issue?)

EmlynB23 commented 2 weeks ago

@doutatsu did you ever resolve this issue? I'm facing the exact same problem

doutatsu commented 2 weeks ago

I don't use Jest anymore, I've switched to Vitest