AbortController not working in React test (Jest) - javascript

We have a function in our React project to fetch a list of stores. If the fetch takes longer than 3 seconds (set low for testing purposes) we abort the request and show an error.
const controller = new AbortController();
const getTimeout = setTimeout(() => controller.abort(), 3000);
const fetchStores = storeId => (
ourFetchStoresFunction(`http://my-api/${storeId}`, {
headers: { 'x-block': 'local-stores' },
signal: controller.signal
})
.then((results) => {
clearTimeout(getTimeout);
return results
})
.catch((err) => { throw err; })
);
I am trying to trigger the Abort error from Jest. I am using Mock Service Worker to intercept fetch requests and mock a delayed response:
import * as StoresAPI from '../Stores-api';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(rest.get(`http://my-api/*`, (req, res, ctx) => {
console.log('TEST');
return res(
ctx.delay(5000),
ctx.status(200),
ctx.json({ stores: ['hi']})
)
}));
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
it('fetchStores should return a stores array', async () => {
await StoresAPI.fetchStores(MOCK_STORES)
.then((stores) => {
expect(Array.isArray(stores)).toBe(true);
})
.catch();
});
When I run this, the delay works, it takes 5000 seconds for the mocked response to fire and the test to pass. But...The test passes and it seems abortController is never called. WHy is this happening? And is there a better way to test this (ideally without using MSW or other library)?

Your test is running synchronously; Jest runs all the code, which includes firing off a Promise but not awaiting it, and then finishes. After the test finishes, the Promise returns, but no one is waiting for it.
The code in the .then block is never even reached by Jest, since it is not awaited.
You can use async code inside Jest tests. I suspect this may give your more mileage:
// mock a quick response for this test
it('returns stores', async () => {
const stores = await StoresAPI.fetchStores(MOCK_STORES)
expect(stores).toEqual([/* returned array */])
})
// mock a long response for this test
it('times out', async () => {
await expect(() => StoresAPI.fetchStores(MOCK_STORES)).rejects.toThrow();
})

Related

How to properly have jest axios mock wait before resolving promise

I have a function like this:
join(): void {
this.working.value = true;
if (this.info.value) {
axios.get('/url')
.then((result: ResultStatus) => {
this.result = result;
})
.catch((reason: AxiosError) => {
this.showError(AjaxParseError(reason));
})
.finally(() => {
this.working.value = false;
});
}
}
and I want to write some unit tests for this. The first unit test I want to write is to test that 'this.saving' is set to true so that I ensure my UI has a value it can use to show a loading indicator.
However, when I use jest to mock axios, jest resolves the axios promise immediately and I don't have a chance to test what happens before the then/finally block is called. Here is what my unit test code looks like:
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
import successResponse from './__json__/LeagueJoinInfoSuccess.json';
describe('constructor:', () => {
let vm: classUnderTest;
beforeEach(() => {
vm = new classUnderTest();
mockedAxios.get.mockResolvedValue({ data: successResponse }); // set up the response
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
await flushPromises();
expect(vm.working.value).toBeFalsy();
});
});
The first expect statement is always false because the finally block is run before I have a chance to do an await flushPromises(); so the working value is always false.
Is there a convenient way to get jest's mock of axios to wait before resolving its promise?
UPDATE: Now here is a really strange thing: If I move the contents of BeforeEach into each of the tests, then it behaves the way that I am hoping it would behave. I guess I will open an issue over at jest and ask them what's going on.
I have a solution for you to create a promise as response, however, we're not gonna resolve it in the 1st test case to keep you test loading state then resolve it in the 2nd test as following:
describe('constructor:', () => {
let vm: classUnderTest;
// Resolve function
let resolveFn: (value?: unknown) => void
beforeEach(() => {
vm = new classUnderTest();
const promise = new Promise(resolve => {
// hold the resolve function to call in 2nd test
resolveFn = resolve;
});
mockedAxios.get.mockImplementation(() => promise);
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
// resolve the pending promise
resolve({
data: successResponse,
});
// I think you would wait the promise resolved
await new Promise(resolve => setTimeout(resolve));
expect(vm.working.value).toBeFalsy();
});
});

How to wait for an async call to finish in component's mount callback before unit testing it?

I'm using Jest to unit test a component that makes an Axios call in it's mounted() method. I'm mocking the Axios call so it returns a promise so the API call is not actually made. But the problem seems to be that because Axios is asynchronous, how do I tell my test to wait for the Axios call (even the fake one) to complete before running my expectations?
This does not work:
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
// The "status" data is set in the axios callback. But this fails.
expect(wrapper.vm.status).toBe('AUTHORIZED');
});
If I wrap it in a timeout, it does work. I think I've read this is because the timeouts are always called after promises are resolved or something?
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
setTimeout(() => {
expect(wrapper.vm.status).toBe('AUTHORIZED');
}, 0);
});
Is there a better way to do this?
Thanks!
Untested, but does the following not work?
const flushPromises = require('flush-promises');
it('calls auth api', async () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
await flushPromises(); // <<
expect(axios.get).toHaveBeenCalledWith('api/auth');
});
(You will need to add https://www.npmjs.com/package/flush-promises (or write your own, it is about 4 lines of code, returning a resolved promise)

Test the status code of a real request to an API with Jest

Hello I'm trying to test this API call but I don't know how to test for the status code of the response since it is a real (and it has to stay like that) API call and not a mock one
this is the function I'm testing:
export const getDataFromApi = (url) => {
return axios.get(url)
.then(({ data }) => data)
.catch(err => console.log(err.toString()));
}
and this is the test:
describe('Read data from API', () => {
test('Get result of the API call', (done) => {
const apiUrl = "https://rickandmortyapi.com/api/character";
getDataFromApi(apiUrl)
.then(data => {
expect(data).toBeDefined();
expect(data.results.length).toBeGreaterThan(0);
done();
});
});
});
how can I expect if the status code of data is 200 or if is another status code?
also is necessary for me to leave that done after the execution of the function? I know with call backs I have to put it but with this promise I'm not sure
Axios has a single response object returned in both the success and error paths which contains the HTTP status code. An error is raised if the response is not in the 2xx range.
You can plumb the status code as a return object from your getDataFromApi() wrapper function, but you'll probably want the full response object for other checks (like headers). I recommend getting rid of the wrapper altogether.
Without the wrapper, here's 2 different status checks using promises, one for success and one for failure:
describe('Read data from API', () => {
test('Get successful result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character";
await axios.get(apiUrl)
.then(r => {
expect(r.data).toBeDefined();
expect(r.data.results.length).toBeGreaterThan(0);
expect(r.status).toBeGreaterThanOrEqual(200);
expect(r.status).toBeLessThan(300);
})
.catch(e => {
fail(`Expected successful response`);
});
});
test('Get failure result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character-bad";
await axios.get(apiUrl)
.then(r => {
fail(`Expected failure response`);
})
.catch(e => {
if (e.response) {
expect(e.response.status).toBeGreaterThanOrEqual(400);
expect(e.response.status).toBeLessThan(500);
} else {
throw e;
}
});
});
});

Jest Unit test, mock implementation of IF condition within function for complete code coverage

I have a API script in a file
const ApiCall = {
fetchData: async (url) => {
const result = await fetch(url);
if (!result.ok) {
const body = await result.text(); // uncovered line
throw new Error(`Error fetching ${url}: ${result.status} ${result.statusText} - ${body}`); // uncovered line
}
return result.json();
},
};
export default ApiCall;
When I mock the call, I have two uncovered lines in code coverage.
Any idea how can I make them cover as well.
Here is what I have tried so far which is not working
it('test', async () => {
ApiCall.fetchData = jest.fn();
ApiCall.fetchData.result = { ok: false };
});
I am kind of new into Jest, so any help would be great.
You need to provide a stubb response in your test spec so that the if statement is triggered. https://www.npmjs.com/package/jest-fetch-mock will allow you to do just that. The example on their npm page should give you what you need https://www.npmjs.com/package/jest-fetch-mock#example-1---mocking-all-fetches
Basically the result is stored in state(redux) and is called from there. jest-fetch-mock overrides your api call/route and returns the stored result in redux all within the framework.
Assuming that what you want to test is the ApiCall then you would need to mock fetch. You are mocking the entire ApiCall so those lines will never execute.
Also, you have an issue, because if you find an error or promise rejection, the json() won't be available so that line will trigger an error.
Try this (haven't test it):
it('test error', (done) => {
let promise = Promise.reject(new Error("test"));
global.fetch = jest.fn(() => promise); //You might need to store the original fetch before swapping this
ApiCall.fetchData()
.catch(err => );
expect(err.message).toEqual("test");
done();
});
it('test OK', (done) => {
let promise = Promise.resolve({
json: jest.fn(() => {data: "data"})
});
global.fetch = jest.fn(() => promise);
ApiCall.fetchData()
.then(response => );
expect(response.data).toEqual("data");
done();
});
That probably won't work right away but hopefully you will get the idea. In this case, you already are working with a promise so see that I added the done() callback in the test, so you can tell jest you finished processing. There is another way to also make jest wait for the promise which is something like "return promise.then()".
Plese post back

How to assert stubbed fetch more than once

Using proxyquire, sinon, and mocha.
I am able to stub fetch on the first call of fetch. But on the second fetch call, which is recursive, I am not able to assert it. From the output, it looks like the assertion may run before the test finishes. You will see this with second fetch console out after assertion.
index.js
var fetch = require('node-fetch');
function a() {
console.log('function a runs');
fetch('https://www.google.com')
.then((e) => {
console.log('first fetch');
b();
})
.catch((e)=> {
console.log('error')
});
}
function b() {
fetch('https://www.google.com')
.then((e) => {
console.log('second fetch');
})
.catch((e)=> {
console.log('error')
});
}
a()
test:
describe('fetch test demo', ()=> {
it('fetch should of called twice', (done)=> {
fetchStub = sinon.stub();
fetchStub2 = sinon.stub();
fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
fetch.returns(Promise.all(promises));
proxy('../index', {
'node-fetch': fetch
});
fetch.should.have.been.callCount(2);
done()
});
});
fetch test demo
function a runs
1) fetch should of called twice
first fetch
second fetch
lifx alert test
- fetch should of called three times
when rain change is over 50%
- should run fetch twice
0 passing (78ms)
2 pending
1 failing
1) fetch test demo fetch should of called twice:
expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (/home/one/github/lifx-weather/foobar.js:5:3)
AssertionError: expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (foobar.js:5:3)
at Context.it (test/bar.js:22:28)
Updated version
#dman, since you updated your test case I owe you an updated answer. Although rephrased, the scenario is still unorthodox - it seems like you want to ignore in a sense the 'law of gravity' even though you know it's right there in front of you.
I'll try to be as descriptive as possible. You have two functions which are doing async stuff by design. a() calls b() sequentially - by the way this is not recursion. Both functions do not notify their callers upon completion / failure, i.e. they are treated as fire-and-forget.
Now, let's have a look at your test scenario. You create 3 stubs. Two of them resolve to a string and one combining their execution using Promise.all(). Next, you proxy the 'node-fetch' module
proxy('./updated', {
'node-fetch': fetch
});
using the stub that returns the combined execution of stubs 1 & 2. Now, if you print out the resolved value of fetch in either function, you will see that instead of a string it's an array of stubs.
function a () {
console.log('function a runs');
fetch('http://localhost')
.then((e) => {
console.log('first fetch', e);
b();
})
.catch((e) => {
console.log('error');
});
}
Which I guess is not the intended output. But let's move over as this is not killing your test anyway. Next, you have added the assertion together with the done() statement.
fetch.should.have.been.callCount(2);
done();
The issue here is that whether you are using done() or not, the effect would be exactly the same. You are executing your scenario in sync mode. Of course in this case, the assertion will always fail. But the important thing here is to understand why.
So, let's rewrite your scenario to mimic the async nature of the behavior you want to validate.
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe('fetch test demo', () => {
it('fetch should of called twice', (done) => {
var fetchStub = sinon.stub();
var fetchStub2 = sinon.stub();
var fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [fetchStub, fetchStub2];
fetch.returns(Promise.all(promises));
proxy('./updated', {
'node-fetch': fetch
});
setTimeout(() => {
fetch.should.have.been.callCount(2);
done();
}, 10);
});
});
As you can see, the only change made was wrapping the assertion within a timer block. Nothing much - just wait for 10ms and then assert. Now the test passes as expected. Why?
Well, to me it's pretty straightforward. You want to test 2 sequentially executed async functions and still run your assertions in sync mode. That sounds cool, but it's not gonna happen :) So you have 2 options:
Have your functions notify callers upon completion and then run your assertions in truly async mode
Mimic the async nature of things using unorthodox techniques
Reply based on original test scenario
It can be done. I've re-factored your provided files a bit so that
can be executed.
index.js
const fetch = require('node-fetch');
const sendAlert = require('./alerts').sendAlert;
module.exports.init = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
sendAlert().then(() => {
resolve();
}).catch(
e => reject(e)
);
})
.catch(e => {
reject(e);
});
});
};
alerts.js
const fetch = require('node-fetch');
module.exports.sendAlert = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
resolve();
}).catch((e) => {
reject(e);
});
});
};
test.js
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe.only('lifx alert test', () => {
it('fetch should of called twice', (done) => {
var body = {
'hourly': {
data: [{
time: 1493413200,
icon: 'clear-day',
precipIntensity: 0,
precipProbability: 0,
ozone: 297.17
}]
}
};
var response = {
json: () => {
return body;
}
};
const fetchStub = sinon.stub();
fetchStub.returns(Promise.resolve(response));
fetchStub['#global'] = true;
var stubs = {
'node-fetch': fetchStub
};
const p1 = proxy('./index', stubs);
p1.init().then(() => {
try {
fetchStub.should.have.been.calledTwice;
done();
} catch (e) {
done(e);
}
}).catch((e) => done(e));
});
});
What you're trying to do though is a bit unorthodox when it comes to
good unit testing practices. Although proxyquire supports this
mode of stubbing through a feature called global overrides, it is
explained here why should anyone think twice before going down
this path.
In order to make your example pass the test, you just need to add an
extra attribute to the Sinon stub called #global and set it to
true. This flag overrides the require() caching mechanism and
uses the provided stub no matter which module is called from.
So, although what you're asking can be done I will have to agree with
the users that commented your question, that this should not be
adopted as a proper way of structuring your tests.
Here is also a alternative way to do this using Promise.all().
Note: this won't work if using fetch's json method and you need to pass data in the resolve() for logic on data. It will only pass in the stubs when resolved. However, it will assert the number of times called.
describe('fetch test demo', () => {
it('fetch should of called twice', () => {
let fetchStub = sinon.stub();
let fetchStub2 = sinon.stub();
let fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
var promise = Promise.all(promises);
fetch.returns(promise);
proxy('../foobar', { 'node-fetch': fetch });
return promise.then(() => {
fetch.should.have.callCount(2);
});
});
});
I have found another way to get things done.
May be this could work for someone.
describe('Parent', () => {
let array: any = [];
before(async () => {
array = await someAsyncDataFetchFunction();
asyncTests();
});
it('Dummy test to run before()',async () => {
expect(0).to.equal(0); // You can use this test to getting confirm whether data fetch is completed or not.
});
function asyncTests() {
array.forEach((currentValue: any) => {
describe('Child', async () => {
it('Test '+ currentValue ,() => {
expect(currentValue).to.equal(true);
})
})
});
}
});
That's how I achieved the assertion on every element of the array. (Array data is being fetch asynchronously).

Categories