11

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-in fetch.

I then tried to use jest.fn instead of jest.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, I import React from 'react';, then import { render, fireEvent, waitFor, screen } from '@testing-library/react';, and finally import App from './App';. I subsequently attempt to mock fetch 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();.

5
  • 1
    have you read this? kentcdodds.com/blog/stop-mocking-fetch Commented Jun 16, 2020 at 9:49
  • Please, provide a way to replicate the problem. There's something wrong happens with your mocks that cannot be explained with snippets you posted. but the mock seems to be ignored, while the built-int fetch seems to be used - it should result in error because JSDOM doesn't provide fetch, and calling jest.spyOn on missing property is not allowed. I see no way how Cannot read property 'then' of undefined error is possible with second mock alone, unless the error refers to another line which may happen if source maps went wrong. Commented Jun 16, 2020 at 11:21
  • @RedBaron FWIW, the article refers to integration and not unit testing and should be treated with caution. Commented Jun 16, 2020 at 11:25
  • @EstusFlask I appended more information to my question. If what I added is not enough (for example if I should add the whole code of my App component) let me know, and I will edit my question. Commented Jun 16, 2020 at 13:42
  • No, unfortunately, it's not enough. I've tried to explain how mocking should be performed. If the answer doesn't help, then the problem is very specific to your project, please, provide a way to reproduce a problem - a repo, etc. Commented Jun 16, 2020 at 14:48

1 Answer 1

23

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the rich answer. The problem with my test turned out to be a beginner's mistake: I was not using beforeEach and afterEach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.