Testing a promise that's expected to fail using Jest - javascript

I am trying to use Jest to test a function that returns a promise.
The function manyUrls expects an array of URLs containing JSON data to be passed. The contents of these URLs are fetched and returned in an array.
I am testing the case where a valid URL string is passed. This throws a TypeError as expected, but I'm having trouble using promises to write a test for that (async/await works fine)
//index.js
const manyUrls = (urls) => {
return Promise.all(urls.map(url => fetch(url)))
.then(responses =>
Promise.all(responses.map(res => res.json()))
).then(texts => {
return Promise.resolve(texts)
}).catch(error => {
return Promise.reject(error)
})
}
//index.test.js
const manyUrls = require("./index")
const url = 'https://jsonplaceholder.typicode.com/todos/1'
test('Expect TypeError if a single string is passed', () => {
// expect.assertions(1);
// try {
// await manyUrls(url);
// } catch (e) {
// expect(e).toBeInstanceOf(TypeError);
// }
return manyUrls(url).catch(e =>
expect(e).toBeInstanceOf(TypeError)
)
})
I have left commented out the bit of code that successfully passes that test case using async/await (if I add async to the callback) but I am trying to make this work with promises. I have followed the documentation on https://jestjs.io/docs/asynchronous but can't seem to get that test to pass. Does anyone know why?
Thanks in advance,

Your return manyUrls(url)... code would work just fine if your manyUrls function returned a Promise - but it doesn't when the urls parameter is not an Array.
There is a big difference between a function that returns a Promise which rejects with an Error and a function which throws an Error. By using return in your test, you are telling Jest that your function is the former, when in actuality your function is the latter.
In manyUrls, the first line, return Promise.all(urls.map(url => fetch(url))) tries to call urls.maps but throws an Error when urls is not an Array (as in the test case). In throwing the Error, no Promise is returned.
Your try/catch implementation works because it catches the Error that manyUrls is throwing. If you want the return implementation to work, then manyUrls must return a Promise that rejects.
The simplest way to update manyUrls to always return a Promise would be to add the async keyword - as in const manyUrls = async (urls) => {. By making it an async function, any Errors thrown from manyUrls will automatically be turned into the rejection value of the returned Promise. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#return_value

Related

Object Promise, impossible to get value

I have some asynchronous function:
async global(){
return 'Test success'
}
I call this in my hook and it must be fulfilled:
const Controller = use('App/Controllers/Http/Controller')
View.global('ShowGlobal', async () => {
const call = new Controller()
const info = await call.global()
console.log(info)
return info
})
At the same time i am getting correct console.log with result 'Test success', but return gives me constantly [object Promise]
What am I doing wrong?
(This is an answer I found)
The code you write in JavaScript is run on one thread, that means that if your code could actually wait for something it will block any of your other code from getting executed. The event loop of JavaScript is explained very well in this video and if you like to read in this page.
A good example of blocking code in the browser is alert("cannot do anything until you click ok");. Alert blocks everything, the user can't even scroll or click on anything in the page and your code also blocks from executing.
Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
x=>console.log(
"does not resolve untill you click ok on the alert:",
x
)
);
Run that in a console and you see what I mean by blocking.
This creates a problem when you want to do something that takes time. In other frameworks you'd use a thread or processes but there is no such thing in JavaScript (technically there is with web worker and fork in node but that's another story and usually far more complicated than using async api's).
So when you want to make a http request you can use fetch but fetch takes some time to finish and your function should not block (has to return something as fast as possible). This is why fetch returns a promise.
Note that fetch is implemented by browser/node and does run in another thread, only code you write runs in one thread so starting a lot of promises that only run code you write will not speed up anything but calling native async api's in parallel will.
Before promises async code used callbacks or would return an observable object (like XmlHttpRequest) but let's cover promises since you can convert the more traditional code to a promise anyway.
A promise is an object that has a then function (and a bunch of stuff that is sugar for then but does the same), this function takes 2 parameters.
Resolve handler: A function that will be called by the promise when the promise resolves (has no errors and is finished). The function will be passed one argument with the resolve value (for http requests this usually is the response).
Reject handler: A function that will be called by the promise when the promise rejects (has an error). This function will be passed one argument, this is usually the error or reason for rejection (can be a string, number or anything).
Converting callback to promise.
The traditional api's (especially nodejs api's) use callbacks:
traditionalApi(
arg
,function callback(err,value){
err ? handleFail(err) : processValue(value);
}
);
This makes it difficult for the programmer to catch errors or handle the return value in a linear way (from top to bottom). It gets even more impossible to try and do things parallel or throttled parallel with error handling (impossible to read).
You can convert traditional api's to promises with new Promise
const apiAsPromise = arg =>
new Promise(
(resolve,reject)=>
traditionalApi(
arg,
(err,val) => (err) ? reject(err) : resolve(val)
)
)
async await
This is what's called syntax sugar for promises. It makes promise consuming functions look more traditional and easier to read. That is if you like to write traditional code, I would argue that composing small functions is much easier to read. For example, can you guess what this does?:
const handleSearch = search =>
compose([
showLoading,
makeSearchRequest,
processRespose,
hideLoading
])(search)
.then(
undefined,//don't care about the resolve
compose([
showError,
hideLoading
])
);
Anayway; enough ranting. The important part is to understand that async await doesn't actually start another thread, async functions always return a promise and await doesn't actually block or wait. It's syntax sugar for someFn().then(result=>...,error=>...) and looks like:
async someMethod = () =>
//syntax sugar for:
//return someFn().then(result=>...,error=>...)
try{
const result = await someFn();
...
}catch(error){
...
}
}
The examples allways show try catch but you don't need to do that, for example:
var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
x=>console.log("never happens, doesn't resolve")
,err=>console.warn("got rejected:",err)
);
Any error thrown or await returning a rejected promise will cause the async function to return a rejected promise (unless you try and catch it). Many times it is desirable to just let it fail and have the caller handle errors.
Catching errors could be needed when you want the promise to succeed with a special value for rejected promises so you can handle it later but the promise does not technically reject so will always resolve.
An example is Promise.all, this takes an array of promises and returns a new promise that resolves to an array of resolved values or reject when any one of them rejects. You may just want to get the results of all promises back and filter out the rejected ones:
const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
urls.map(//map array of urls to array of promises that don't reject
url =>
fetch(url)
.then(
undefined,//do not handle resolve yet
//when you handle the reject this ".then" will return
// a promise that RESOLVES to the value returned below (new Fail([url,err]))
err=>new Fail([url,err])
)
)
)
.then(
responses => {
console.log("failed requests:");
console.log(
responses.filter(//only Fail type
isFail
)
);
console.log("resolved requests:");
console.log(
responses.filter(//anything not Fail type
response=>!isFail(response)
)
);
}
);

Trouble with resolving promises in sequence

I'm trying to resolve an array of promises in the build process of a NextJS app since I'm getting network errors when using Promise.all, but for some reason I'm having trouble resolving the promises.
This code works, but does not work with my build:
const activityPlacesPromises = activityLocationIDs.map((id) =>
this.get(`places/${id}`)
);
const activityPlaces = await Promise.all(activityPlacesPromises);
console.log(activityPlaces); // Returns the data correctly
This code does not work:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise.then(() => this.get(`places/${id}`));
}, Promise.resolve());
console.log(activityPlaces); // Returns Promise { <pending> }
Why isn't the Promise.resolve() reduce function working?
PS: I am going off of this SO question: Resolve promises one after another (i.e. in sequence)?
activityPlace is still just a promise you will need to await
console.log(await activityPlaces);
Note that you're not doing anything with the result of each promise (aside from the last one)
Wouldn't it be way easier to just throw this in a regular for loop and await one by one? the reduce pattern is useful for cases where you don't have async/await, but you don't seem to have this limitation:
const results = [];
for(const id of activityLocationIDs) {
results.push(await this.get(`places/${id}`));
}
console.log(result);
The above code matches the behavior of your first sample.
It looks like you are trying to avoid using async/await, firstly perhaps you should catch any errors, to allow logging and execution to continue:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise
.then(() => this.get(`places/${id}`))
.catch((err) => console.error(`Error getting place ID: ${id}`, err))
}, Promise.resolve());
activityPlaces.then(() => console.log(activityPlaces));
Also, consider that since your code is no longer using async, your build may not wait for the promises to resolve before ending.

Passing parameters to a promise

I am trying to write a promise in such a way that I can pass some parameters. Inside, this promise will call a Fetch. This is the code I wrote so far:
const myFunction = (parA, parB, parC) => {
return new Promise ((resolve, reject) => {
url = ... // calculated based on the parameters passed above;
fetch(url)
.then(response => {
var object = response.json();
resolve(object);// the idea is that 'object' is returned by the outmost promise
console.log('Check');// So far OK, this is correctly printed to the console
})
.catch(err => {
console.log('Error: ' + err);
reject(err);
});
// resolve('1') // Used for test only. With this, everything works, but "object" here is undefined -- I guess it gets executed too early, before the completion of the Fetch
});
and this is where the promise is called
myFunction(a, b, c).then(res => {
console.log('OK');// just testing
console.log(res);// just testing
});
What happens is that the Fetch resolves OK, but my overall promise doesn't. The last two console.log instructions are never executed.
In short, my problem is: how can I resolve my promise returning the result from the Fetch? It's a promise inside a promise, I guess I could chain them with .then, but I also want to be able to pass parameters.
I also guess I could rewrite the code avoiding this promise chaining, but since I'm also doing it as a way of learning, I prefer to try to figure out first how to solve this problem with this structure.
p.s.: at the moment I am stuck with the promises, cannot use async/await because I am with an old version of node.js and cannot update it
As #VLAZ already mentioned, it's not necessary to create your own promise here, since fetch() itself already returns a promise.
Therefore, you could try this:
const myFunction = (parA, parB, parC) => {
const url = ... // calculated based on the parameters passed above;
return fetch(url)
.then(response => response.json())
.then(object => {
console.log('Check', object);
return object;
})
.catch(err => {
console.log('Error: ' + err);
});
};

Jest - TypeError: response.json is not a function

We are unit testing a React-Native application (using Jest) which does various API calls using fetch.
We have mocked calls to fetch in our API call functions in order to test them. This works well so far. We also have functions that combine these API calls and operate some logic on them.
For example, here is one function that, given a token, will get the related user's first project (project[0]) and return the list of items from this project.
export async function getAllItems(token) {
try {
const response = await getCurrentUser(token); // fetch called inside
const responseJson = await response.json();
const allItemsResp = await getAllItemsFromSpecificProject(
token,
responseJson.projectIds[0],
); // fetch called inside
return await allItemsResp.json();
} catch (error) {
console.log(error);
return null;
}
}
Both functions getCurrentUser and getAllItemsFromSpecificProject are simple fetch calls and are currently mocked properly. Here one test that tries to test the getAllItems function:
it('Gets all items', async () => {
getAccessTokenMockFetch();
const token = await getAccessToken('usherbrooke#powertree.io', 'test!23');
getAllItemsMockFetch();
const items = await getAllItems(token.response.access_token);
expect(items.response.length).toEqual(3);
});
For clarity, here is how getAccessTokenMockFetch is done. getAllItemsMockFetch is almost identical (with different data in the response):
function getAccessTokenMockFetch() {
global.fetch = jest.fn().mockImplementation(() => {
promise = new Promise((resolve, reject) => {
resolve(accepted);
});
return promise;
});
}
where accepted contains the JSON content of a successful call. When we run this test, we get the following error:
TypeError: Cannot read property 'response' of null
And we console.log this one in the catch:
TypeError: response.json is not a function
which explains why response is null. It seems the json() call is not understood and I don't know how to mock it. I have done tons of research on Stack Overflow and beyond, but we have found nothing that helps me understand how to solve this issue. This might indicate that I am going the wrong way about this, which is quite possible since I'm new to JavaScript, React Native, and Jest.
One thing to try is giving it a fake json to call, like this:
const mockFetch = Promise.resolve({ json: () => Promise.resolve(accepted) });
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);

Catching a promise without reject

I was going through express-async-handler code
const asyncUtil = fn =>
function asyncUtilWrap(...args) {
const fnReturn = fn(...args)
const next = args[args.length-1]
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncUtil
Here they have used .catch without rejecting a promise and created a promise without using a promise constructor (new Promise(resolve, reject))
We use the above snippet of code like this
const asyncHandler = require('express-async-handler')
express.get('/', asyncHandler(async (req, res, next) => {
const bar = await foo.findAll();
res.send(bar)
}))
Can someone help me in comprehending what I am missing here?
fnReturn might be a promise, which would mean that the promise created with Promise.resolve would adopt it. The adopted promise might throw an error which needs catching
const fnReturn = fn(...args)
Here fn might be returning a promise which is getting passed as parameter to Promise.resolve and every time below code will return an resolved promise even if fnReturn throws an error and in this catch block may not be executed on rejection.
And more over Promise.resolve or Promise.reject is an another approach to return resolvedPromise value or rejected promise value.
please find below snippet to understand it better.
let a = Promise.resolve(13);
a.then((value)=>{console.log(value)});
More over to add on to this foo.findAll() returns a promise which we consume it using async await based on latest ES6 features and if you don't mind consume await under an try block so that if any error is encountered then it can be captured in catch block.
try{
const bar = await foo.findAll()
}
catch(exception){
console.log(exception);
}
created a promise without using a promise constructor
I saw something wrong in previous answer : fnReturn doesn’t have to be a Promise itself. It even has nothing to do with it. As specified in documentation :
The resolve function returns either a new promise resolved with the passed argument, or the argument itself if the argument is a promise produced by this constructor.
ecma.org
Which means, in other word, that argument passed to Promise.resolve() can be a synchrone function, which will be wrapped inside a new Promise. So that answer your first question : they don’t use Promise constructor because they don’t have to, javascript is doing it for them (like in a try{…} catch{…} function).
Here they have used .catch without rejecting a promise
When trying to figure out something, it is always interesting to play a bit around with your code. Let’s take a look at the following snippet:
// Original code
const asyncUtil = fn =>
function asyncUtilWrap(...args) {
const fnReturn = fn(...args);
const next = args[args.length-1];
return Promise.resolve(fnReturn).catch(next);
};
// Simulation
const req = {
url: "random"
};
const res = {
html: "rendered"
};
function next(err) {
alert(err.message);
return
}
asyncUtil(async (req, res, next) => {
// throw new Error("An error occured here");
const result = await new Promise(resolve => {
setTimeout(() => {resolve("Result of async function")}, 1000);
});
alert(result);
})(req, res, next);
This code intend to simulate an express environment… well, at least in the way we are interested in. I defined req and res as 2 fake objects, and next() to just display our error, if any. asyncUtil() will launch a function that resolve after 1 second, and display its result.
First run of the snippet should give the following result :
Notice the 3 commented lines. Here, everything went fine, and we got our result displayed. So what happens exactly here.
const fnReturn = fn(...args); This line assign our async function to fnReturn (fnReturn = async (req, res, next) => {. . .}). So when we call Promise.resolve(fnReturn), what it actually does is wrapping it inside a Promise like this :
new Promise(resolve => {
resolve(fnReturn);
});
Once completed, it will asynchronously return the result of fnReturn to callback.
Now our promise can only resolve, meaning it doesn’t have a reject on itself. But that doesn’t mean errors can’t occur. If you try to uncomment the following line :
throw new Error("An error occured here");
What we’ll do is throwing a fake error at the beginning of our function. Notice the result when running this :
Our regular script wasn’t executed (it breaks after an error), and we moved to our next() function !
Our Promise doesn’t have any error handler, but if an error is thrown, even outside a reject(), it’ll still propagate. Here, the Promise can’t resolve as it is broke in process, and our catch() will be able to block the exception, avoiding your whole code shutting down.
Hope it was clear enough, feel free to ask if anything wasn’t understandable !

Categories