I have an action that uses Promise.allSettled to return multiple stat objects from an API.
When I run the tests with mocking I get the error
Promise.allSettled is not a function
I have a get endpoint to returns different types of stats.
myapi.com/get_stats/:type
I have an action for this API as follows
const types = ['seo', 'referrers', 'clicks', 'posts', 'videos'];
const promises = [];
types.forEach(type =>
promises.push(
api().stats.getStats(type),
),
);
Promise.allSettled(promises).then(res => {
const { statData, statErrors } = mapData(res); // Data manipulation
dispatch({ type: FETCH_STATS_RESOLVED, payload: { statData, statErrors } });
});
My Test set up
jest.mock('api-file.js', () => ({
api: () => ({
stats: {
getStats: type => {
switch (type) {
case: 'seo':
return mockSeoStats;
}
}
}
})
}));
in beforeEach()
mockSeoStats.mockImplementation(() => Promise.resolve({ value: {data: myData} }));
I can see that the Action is receiving these mocked values, but Promise.allSettled is complaining
I assume it's having a hard time with the jest mock structure
So, how can i mock Promise.allSettled to just return what I expect it to, instead of looking at my mocked functions?
A similar question was asked at Execute batch of promise with Promise.allSettled()
Promise.allSettled is available from Node version 12.0 +
You can update node using Node's version manager
nvm ls
# Look for a 12.17.0 version (latest)
nvm install 12.17.0
# Automatically switches to new version
npm run test
# Should properly run your Promise.all
Hope that helped.
Try wrapping Promise.allSettled in try-catch block if you don't want to update node js.
Example:
try {
Promise.allSettled([
// your code
]).finally(() => {
// your code`enter code here`
});
} catch (e) {
console.log('promise.allSettled', e);
}
Related
I planned updating some Promise.all to Promise.allSettled in my React Native - Expo Project but the function does not Exist. i checked all Versions and everything fits but i still cant use the function.
node -v: 14.15.4
expo SDK -v version: ^40.0.1
expo uses react native -v 0.63 in their SDK version. This should not be the problem. This is the error message:
Promise.allSettled is not a function
Does anybody knows a solution to this? Thank you for your help!
For anyone coming across this issue, apparently this was fixed in v64:
https://github.com/facebook/react-native/issues/30236#issuecomment-939286987
For older versions, you can use a simple polyfill:
Promise.allSettled = Promise.allSettled || ((promises) => Promise.all(
promises.map(p => p
.then(value => ({
status: "fulfilled",
value
}))
.catch(reason => ({
status: "rejected",
reason
}))
)
));
React Native uses https://github.com/then/promise for Promises and only in version 8.2.0 it was added with allSettled.
React Ntaive team updated to this version only in 0.70.6 (here list of changes including promise version bumping to 8.3.0 https://github.com/facebook/react-native/releases/tag/v0.70.6)
export const PromiseHelperAllSettled = (promises) => {
return Promise.all(promises.map(function (promise) {
return promise.then(function (value) {
return { state: 'fulfilled', value: value };
}).catch(function (reason) {
return { state: 'rejected', reason: reason };
});
}));
};
import { PromiseHelperAllSettled } from './PromiseHelperAllSettled';
useEffect(() => {
if (Promise && !Promise.allSettled) {
Promise.allSettled = PromiseHelperAllSettled;
}
},[]);
- UPDATE -
The issue has been identified.
In the actual codebase the assertion is passed to a imported callback, and once the callback executes with a failed test, it raises a promise rejection.
So, this is close to how the test was actually written:
describe( "file system", () => {
it( "should check if the file exists", async () => {
call( async () => {
const received = await fileExists();
const expected = true;
expect( received ).toBe( expected );
});
});
});
and the complex callback is presented in a simpler way to produce the same issue:
export function call( callback) {
callback();
}
- UPDATE -
The following code works.
I picked up a small portion of the code from a large codebase for better visibility. If I run just the following piece of code, it works as expected. I think there's an issue in the actual codebase.
#Flask's recommendation of handling the unhandled promise rejections centrally added a great value to the question.
Consider the following test:
import fileExists, { call } from "./exists";
describe( "file system", () => {
it( "should check if the file exists", async () => {
const received = await fileExists();
const expected = true;
expect( received ).toBe( expected );
});
});
for the following source:
import fs, { constants } from "fs";
import { promisify } from "util";
export default async function fileExists() {
const path = ".nonexistent";
const access = promisify( fs.access );
try {
await access( path, constants.F_OK );
} catch {
return false;
}
return true;
}
When fileExists rejects returns false, an UnhandledPromiseRejectionWarning is received as expected. But this does not help trace the source of the failed test.
For synchronous tests, Jest shows the path to the test (i.e. file system › should check if the file exists) which helps trace the source of the failed test.
What is the best way to achieve this for asynchronous tests?
UnhandledPromiseRejectionWarning is not expected here. It's not equivalent to failed test because it doesn't prevent a test to pass if assertions pass. It means that the code was written the wrong way and contains unchained promises.
It can happen only if await was omitted in the test:
fileExists(); // no await
Or fileExists function contains loose unhandled promises:
fileExists() {
whatever() // no return
.then(() => {
whatever() // no return
}) // no catch to suppress errors
}
It's a good practice to have in setupFiles:
process.on('unhandledRejection', console.error);
It provides more useful output than UnhandledPromiseRejectionWarning and allows to debug the problem based on error stack.
I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.
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
I'm new to unit testing, and I'm aware my tests may not be valuable or following a specific best practice, but I'm focused on getting this working, which will allow me to test my frontend code using JSDOM.
const { JSDOM } = require('jsdom');
const { describe, it, beforeEach } = require('mocha');
const { expect } = require('chai');
let checkboxes;
const options = {
contentType: 'text/html',
};
describe('component.js', () => {
beforeEach(() => {
JSDOM.fromFile('/Users/johnsoct/Dropbox/Development/andybeverlyschool/dist/individual.html', options).then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
});
});
describe('checkboxes', () => {
it('Checkboxes should be an array', () => {
expect(checkboxes).to.be.a('array');
});
});
});
I'm getting the error "AssertionError: expected undefined to be an array". I'm simply using the array test as a test to ensure I have JSDOM functioning correctly. There are no other errors occurring. Any help would be much appreciated!
fromFile is an async function, meaning that by the time your beforeEach() has finished and the tests start running, it is (probably) still loading the file.
Mocha handles async code in two ways: either return a promise or pass in a callback. So either return the promise from fromFile or do this:
beforeEach(function(done) {
JSDOM.fromFile(myFile)
.then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
})
.then(done, done);
});
The promise version looks like this:
beforeEach(function() {
return JSDOM.fromFile(myFile)
.then((dom) => {
checkboxes = dom.window.document.querySelectorAll('.checkbox');
});
});