Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to test api calls using fetch? #967

Closed
bondarewicz opened this issue Oct 26, 2016 · 11 comments
Closed

how to test api calls using fetch? #967

bondarewicz opened this issue Oct 26, 2016 · 11 comments

Comments

@bondarewicz
Copy link

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

@gaearon
Copy link
Contributor

gaearon commented Oct 26, 2016

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

@bondarewicz
Copy link
Author

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

tried that no difference ...

@gaearon
Copy link
Contributor

gaearon commented Oct 26, 2016

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);
  });

@bondarewicz
Copy link
Author

bondarewicz commented Oct 26, 2016

@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?

@bondarewicz
Copy link
Author

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);
    });
  });

@gaearon
Copy link
Contributor

gaearon commented Oct 26, 2016

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.

@silvenon
Copy link

silvenon commented Nov 6, 2016

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.

@gaearon
Copy link
Contributor

gaearon commented Nov 20, 2016

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

@gaearon gaearon closed this as completed Nov 20, 2016
@john-doherty
Copy link

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!

@tkrotoff
Copy link

tkrotoff commented Jun 13, 2018

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

@j18s
Copy link

j18s commented Jan 15, 2019

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?

@lock lock bot locked and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants