How to unit test API calls with mocked fetch() in

2020-02-11 17:07发布

问题:

In React Native I use fetch to perform network requests, however fetch is not an explicitly required module, so it is seemingly impossible to mock in Jest.

Even trying to call a method which uses fetch in a test will result in:

ReferenceError: fetch is not defined

Is there a way to test such API requests in react native with Jest?

回答1:

Inside your test case you can mock any function you want by using Jest's mocks:

fetch = jest.fn(() => Promise.resolve());

This approach works only for the promise-based test cases (see pit in the Jest docs).

As far as fetch is an async function, you need to run all your tests using pit (read more about async tests here).



回答2:

Another approach where you mock the global fetch object:

const mockSuccesfulResponse = (
  status = 200,
  method = RequestType.GET,
  returnBody?: object
) => {
  global.fetch = jest.fn().mockImplementationOnce(() => {
    return new Promise((resolve, reject) => {
      resolve({
        ok: true,
        status,
        json: () => {
          return returnBody ? returnBody : {};
        },
      });
    });
  });
};

The above helper method can be modified any way you want :-) Hope it helps someone



回答3:

Rather than rolling your own mock, you can use the jest-fetch-mock npm package to override the global fetch object. That package allows you to set up fake responses and verify sent requests. See that link for extensive usage examples.



回答4:

I solved this by adding isomorphic-fetch.

$ npm install --save isomorphic-fetch

and using it like

import fetch from 'isomorphic-fetch';
...
fetch('http://foo.com');

whatwg-fetch might work as well



回答5:

As @ArthurDenture recommended, you can use fetch-mock, but there are some additional packages you will need to install to make it work with React Native and Jest:

$ npm install --save-dev fetch-mock
$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save-dev babel-preset-env

You can then mock fetch requests in your tests. Here is an example:

// __tests__/App.test.js
import React from 'react';
import App from '../App';
import fetchMock from 'fetch-mock';
import renderer from 'react-test-renderer';

it('renders without crashing', () => {
  fetchMock.mock('*', 'Hello World!');
  const rendered = renderer.create(<App />).toJSON();
  expect(rendered).toBeTruthy();
});


回答6:

Suppose you want to test resolve and reject cases, for this first you mock the fetch behaviour and then use Jest's rejects and resolves methods with with assertion block


function fetchTodos() {
  return fetch(`${window.location.origin}/todos.json`)
    .then(response => response.json())
    .catch(error => console.log(error))
}
describe('fetchTodos', () => {
  it('returns promise resolving to parsed response', () => {
    global.fetch = jest.fn(() => Promise.resolve({ json: () => ''}))
    expect(fetchTodos()).resolves.toBe('');
  })
  it('returns promise handling the error', async () => {
    global.fetch = jest.fn(() => Promise.reject(''))
    expect(fetchTodos()).rejects.toBe('')
  })
})



回答7:

As shown in the react-testing-library documentation, you can use the jest.spyOn() function, which will mock the fetch function only for the next time it is called.

const fakeUserResponse = {token: 'fake_user_token'}
jest.spyOn(window, 'fetch').mockImplementationOnce(() => {
  return Promise.resolve({
    json: () => Promise.resolve(fakeUserResponse),
  })
})

react-testing-library