How to mock fetch when testing a React app?
That there's ECONNREFUSED
error instead of fetch is not defined
means that fetch
has been polyfilled. It's not a part of JSDOM and isn't polyfilled by Jest itself but is specific to current setup. In this case the polyfill is provided by create-react-app.
It's always preferable to mock existing global function with jest.spyOn
and not by assigning them as global
properties, this allows Jest to do a cleanup. A thing like global.fetch = jest.spyOn(global, 'fetch')
should never be done because this prevents fetch
from being restored. This can explain TypeError: Cannot read property 'then' of undefined
error for seemingly correctly mocked function.
A correct and safe way to mock globals is to mock them before each test and restore after each test:
beforeEach(() => {
jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest.fn().mockResolvedValue(mockResponse)
})
});
afterEach(() => {
jest.restoreAllMocks();
});
There should be no other modifications to global.fetch
in order for a mock to work correctly.
A preferable way to restore mocks and spies is to use configuration option instead of jest.restoreAllMocks
because not doing this may result in accidental test cross-contamination which is never desirable.
Another reason for TypeError: Cannot read property 'then' of undefined
error to appear is that Jest incorrectly points at fetch
line, and the error actually refers to another line. This can happen if source maps don't work correctly. If fetch
is mocked correctly and there are other then
in the same component, it's a plausible explanation for the error.
Comments
-
Giorgio almost 2 years
I would like to test a small React web app where I use the global
fetch
method.I tried to mock
fetch
in this way:global.fetch = jest.spyOn(global, 'fetch').mockImplementation(endpoint => Promise.resolve({ json: () => Promise.resolve(mockResponse) }) );
... but the mock seems to be ignored, while the built-in
fetch
seems to be used:Error: connect ECONNREFUSED 127.0.0.1:80 ...
looks like a failed call to the built-infetch
.I then tried to use
jest.fn
instead ofjest.spyOn
:global.fetch = jest.fn(endpoint => Promise.resolve({ json: () => Promise.resolve(mockResponse) }) );
... and was surprised to see a different error. Now the mock seems to be taken into consideration, but at the same time is not working correctly:
TypeError: Cannot read property 'then' of undefined 8 | this.updateTypes = this.props.updateTypes; 9 | this.updateTimeline = this.props.updateTimeline; > 10 | fetch('/timeline/tags') | ^ 11 | .then(res => res.json()) 12 | .then(tags => tags.map(tag => <option value={tag} key={tag} />)) 13 | .then(options => options.sort((a, b) => a.key.localeCompare(b.key)))
I find the documentation of Jest and React Testing Library a bit confusing, honestly. What might be the problem with what I am doing?
Edit
The React component I am trying to test is called "App", was generated with Create React App, and was changed to include a call to
fetch
. I can gladly provide the code for this component, but I believe that the problem lies in the tests.At the beginning of my
App.test.js
file, Iimport React from 'react';
, thenimport { render, fireEvent, waitFor, screen } from '@testing-library/react';
, and finallyimport App from './App';
. I subsequently attempt to mockfetch
in one of the ways I described, and then declare the following test:test('renders a list of items, upon request', async () => { const app = render(<App />); fireEvent.click(screen.getByText('Update')); await waitFor(() => screen.getByRole('list')); expect(screen.getByRole('list')).toBeInTheDocument(); expect(screen.getByRole('list')).toHaveClass('Timeline'); });
Finally, I end my test file with
global.fetch.mockRestore();
. -
Giorgio almost 4 yearsThank you for the rich answer. The problem with my test turned out to be a beginner's mistake: I was not using
beforeEach
andafterEach
.