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
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?
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
results in :
can this be considered as good practice for testing promises returned by fetch in jest?