Create-react-app: how to test api calls using fetch?

Created on 26 Oct 2016  路  11Comments  路  Source: facebook/create-react-app

I'm having a hard time understanding below behaviour and would appreciate any help to pointing me into the right direction.

I have a generic class for my api calls:

class Api {
 static ack(param1, param2) {
  return fetch(process.env.REACT_APP_ACK_URL + param1 + '/' + param2, {
          method: 'PUT',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          }
        })
        .then((response) => {
          if(response.ok) {
            return response.json();
          } else {
            console.log('ack failed', response.statusText)
          }
        })
        .catch((ex) => {
          throw new Error('fetch failed' + ex)
        });
 }
}

in my Api.test.js I have a following code

import Api from './Api';

describe("Api", function () {

  beforeEach(function() {
    window.fetch = jest.fn().mockImplementation(() => Promise.resolve({ok: true, Id: '123'}));
  });

  it("ack", function () {

    Api.ack('foo', 'bar').then(response => {
      console.log(response); //< this is never printed
      expect(response.Id).toBe(1); //< always pass / gets ignored?
    });

  });
});

regardless of what I set the response.Id in Promise.resolve({ok: true, Id: '123'})

expect(response.Id).toBe(1) or expect(response.Id).toBe('abc') always pass

Most helpful comment

@gaearon you rock dude, thanks so much :)
I ended up changing the mockImplementation as per below and using async/await

Api.test.js

beforeEach(function() {

  global.fetch = jest.fn().mockImplementation(() => {
      var p = new Promise((resolve, reject) => {
        resolve({
          ok: true, 
          Id: '123', 
          json: function() { 
            return {Id: '123'}
          }
        });
      });

      return p;
  });

});


it("ack", async function() {
  const response = await Api.ack('foo', 'bar');
  console.log(response);
  expect(response.Id).toBe(1); 
});

results in :

expect(received).toBe(expected)

Expected value to be (using ===):
  1
Received:
  "123"

Difference:

Comparing two different types of values:
  Expected: number
  Received: string

can this be considered as good practice for testing promises returned by fetch in jest?

All 11 comments

Try setting global.fetch instead? I'd expect it to be used in Node environment.

@gaearon you mean like below?
global.fetch = jest.fn().mockImplementation(() => Promise.resolve({ok: true, CorrelationId: '123'}));

tried that no difference ...

By the way I noticed you don't return Promise from the test. The runner can't know to wait for the promise to finish in this case.

Have you tried returning Promise or using async await? See the async tutorial.

  it("ack", async function () {
    const response = await Api.ack('foo', 'bar');
    console.log(response);
    expect(response.Id).toBe(1);
  });

@gaearon you rock dude, thanks so much :)
I ended up changing the mockImplementation as per below and using async/await

Api.test.js

beforeEach(function() {

  global.fetch = jest.fn().mockImplementation(() => {
      var p = new Promise((resolve, reject) => {
        resolve({
          ok: true, 
          Id: '123', 
          json: function() { 
            return {Id: '123'}
          }
        });
      });

      return p;
  });

});


it("ack", async function() {
  const response = await Api.ack('foo', 'bar');
  console.log(response);
  expect(response.Id).toBe(1); 
});

results in :

expect(received).toBe(expected)

Expected value to be (using ===):
  1
Received:
  "123"

Difference:

Comparing two different types of values:
  Expected: number
  Received: string

can this be considered as good practice for testing promises returned by fetch in jest?

BTW with the same mockImplementation as above, below now prints the response into console but expect is still passing

it("ack", function () {
    Api.ack('foo', 'bar').then(response => {
      console.log(response);
      expect(response.Id).toBe(1);
    });
  });

The test is likely passing because it has already finished executing, and so even throwing can't influence that particular test.

can this be considered as good practice for testing promises returned by fetch in jest?

Sounds reasonable to me.

Another way of testing API calls is node-fetch + nock. That way you can also test if your API calls are using expected HTTP methods, sending the expected body etc. Tests might also be slightly easier to read.

Going to close since this is a usage question and not something we could/need to fix.

I stumbled upon this post after burning hours trying to unit test fetch requests. All the mock fetch modules I could find on NPM were overly complex or required too much setup time, so I created fetch-reply-with.

It simply adds a .replyWith property to the existing fetch options object:

require('fetch-reply-with'); // <- fetch is now globally available within the node environment

// add a fetch reply for GET http://www.orcascan.com
fetch('http://www.orcascan.com', {

    // regular fetch option
    method: 'GET',

    // add reply for this fetch
    replyWith: {
        status: 200,
        body: 'Bulk Barcode Scanning app',
        headers: {
            'Content-Type': 'text/html'
        }
    }
})
.then(function(res) {
    // handle the response within the first then
    // as suggested by https://github.com/kevmarchant
});

// or at some point in the future
fetch('http://www.orcascan.com').then(function(res){
    return res.text();
})
.then(function(text){
    // text now equals Bulk Barcode Scanning app
});

Hope it helps the next frustrated developer!

For people passing by, simplest working solution:

function mockFetch(data) {
  return jest.fn().mockImplementation(() =>
    Promise.resolve({
      ok: true,
      json: () => data
    })
  );
}

test('fetchPerson()', async () => {
  fetch = mockFetch(someJson); // or window.fetch

  const person = await fetchPerson('whatever id');
  expect(person).toEqual(someJson);

  // Make sure fetch has been called exactly once
  expect(fetch).toHaveBeenCalledTimes(1);
});

when testing this simple function:

function fetchPerson(id) {
  const response = await fetch(`${BASE_URL}/people/${id}`);
  if (!response.ok) throw new Error(response.statusText);
  const data = await response.json();
  // Some operations on data if needed...
  return person;
}

Jest configuration:

// File jest.setup.js
import 'whatwg-fetch';
// File jest.config.js
module.exports = {
  setupFiles: ['./jest.setup.js'],
};

(because Jest uses Node.js and Node.js does not come with fetch => specific to web browsers)

Real life simple example: https://github.com/tkrotoff/MarvelHeroes

jest.fn().mockImplementation
Thanks for sharing this, but I am unclear why to add mockImplementation. For me its working with jest.fn(). Can you please tell me the benefit by adding mockImplementation?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adrice727 picture adrice727  路  3Comments

stopachka picture stopachka  路  3Comments

xgqfrms-GitHub picture xgqfrms-GitHub  路  3Comments

Aranir picture Aranir  路  3Comments

ap13p picture ap13p  路  3Comments