How to make catched promises fail in jest - javascript

Consider this function:
aPromise = require('axios');
function middleware(callback) {
axios.get('/api/get')
.then(callback)
.catch(callback);
}
Consider this test:
const callback = (err) => {
expect(isError(err)).toBe(true);
done();
};
middleware(callback);
The isError is a lodash function.
Consider aPromise as something I want to test. If the promise always resolves, this test should not pass. But it will! And that's because the promise's catch actually catches the expect exception.
My question is: How to not catch the error in a promise's catch handler when expect throws an error in the promise's then handler?
Note that I don't use async/await.

You need to create a failed promise and you need to return the promise in your test. Please have a look on the doc on testing promises.
aPromise = require('axios');
jest.mock('axios', () => {
get: ()=> jest.fn() //initialy mock the get function
})
it('catch failing promises',() = > {
const result = Promise.reject('someError'); //create a rejected promises
aPromise.get.mockImplementation(() => result)// let `get` return the rejected promise
const callback = jest.fn()
middleware(callback)
return result
.then (()=>{
expect(callback).toHaveBeenCalledWith('someError');
})
})

Related

Handling Promise rejection with catch while using await

I am using await to make the code cleaner, but I am not sure whether I am handling exceptions correctly.
An example while using azure-devops-node-api;
const foo = async() => {
return new Promise((resolve, reject) => {
...
...
const teams = await coreApiObject.getTeams(currProject.id)
.catch(err => { reject(err) return })
...
...
})
}
In this code I am assuming, if there is a problem with promise call, foo() is going to return reject.
async functions always return a promise, so you don't need to explicitly create one yourself. Any non-promise value returned from an async function is implicitly wrapped in a promise.
Inside the foo function, you just need to await the call coreApiObject.getTeams(...) and to catch and handle any error, use the try-catch block.
Your code can be simplified as shown below:
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (e) {
// handle error
}
}
If you want to the calling code to handle the error, then you can use one of the following options:
Remove the try-catch block and just return the result of coreApiObject.getTeams(...).
const foo = async() => {
return coreApiObject.getTeams(currProject.id);
}
Removing the try-catch block and just returning the call to coreApiObject.getTeams(...) will allow the calling code to handle the error because the promise returned by the foo function will get resolved to the promise returned by coreApiObject.getTeams(...); this means that the fate of the promise returned by the foo function will depend on whatever happens to the promise returned by coreApiObject.getTeams(...).
If the promise returned by coreApiObject.getTeams(...) is rejected, promise returned by the foo function will also be rejected and hence the calling code will have a change to catch the promise rejection and handle it.
Throw the error from the catch block.
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (error) {
// throw the error
throw error;
}
}
Other option is to throw the error from the catch block to make sure that the promise returned by the async function is rejected.
If you don't throw the error or return a promise or a thenable that is rejected, returning any other value from the catch block will fulfil the promise returned by the async function with whatever value is returned inside the catch block.

Timeout exceeded for Mocha promises

I have a async function that awaits a promise which resolves when it receives some 'data'. However, when I run the test, I get a Error: Timeout of 300000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
Here is my code snippet, I am using this in truffle to test solidity contracts :
contract("Test", async (accounts) => {
it("test description", async () => {
let first = await getFirstEvent(oracle.LogResult({fromBlock:'latest'}));
let second = await getFirstEvent(oracle.LogResult({fromBlock:'latest'}));
Promise.all([first,second]);
//some assertion code
});
const getFirstEvent = (_event) => {
return new Promise((resolve, reject) => {
_event.once('data', resolve).once('error', reject)
});
}
});
Isn't the promise resolving ? I can see 'data' coming back in the callback because I am emitting the callback event in the solidity code I am testing.
I managed to resolve this issue, so posting it here so that others can use the approach.
I created a Promise that times out after a duration we can set :
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {a
reject(new Error('Request timed out'));
}, 200000);
})
Then, I race the timeoutPromise with the Promise which is fetching data, like this for the case I posted :
Promise.race([getFirstEvent(oracle.LogResult({fromBlock:'latest'})), timeoutPromise]);
It looks to me like there's a few things wrong here.
First of all, your function isn't returning anything i.e. it should be return Promise.all([first, second]);.
Secondly, if the goal of the promise.all is to execute the promises in parallel, then that's not what it's doing here because you already have await statements on those function calls above. What you are looking for here would be:
return await Promise.all([
getFirstEvent(oracle.LogResult({fromBlock:'latest'}),
getFirstEvent(oracle.LogResult({fromBlock:'latest'})]);
Now in terms of the promise not resolving, I'm assuming the event is generated from oracle.LogResult(). In this case, what you'd want to do is setup your promises to listen for the event first, for example:
let first = getFirstEvent();
let second = getSecondEvent();
Now you have 2 promises that are listening for the events. Next, you generate the event:
oracle.LogResult({ fromBlock: 'latest' });
oracle.LogResult({ fromBlock: 'latest' });
Finally, you ensure you wait on the result of the promises:
return await Promise.all([first, second]);

Optional catch in javascript promises

The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined
Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}
I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}
You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))
Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));

.catch function doesnt catch rejected value

Hi everyone!
I use Node.js v8.5.0.
And this is my code in Node.js (simplified):
// function that returns promise
const request = (url) =>
new Promise((resolve, reject) => {
superagent
.get(url)
.end((err, res) => {
if (err || !res.ok) {
reject(err);
return;
}
resolve(res);
})
})
// function that add .then and .catch to promise
const to = promise =>
promise
.then((data) => [null, data])
.catch((err) => [err]);
// making request
let [err, result] = await to(request());
When i do request and some error occurs .catch function doesnt catch rejected value and i get error like Unhandled Promise Rejection. But, in fact I added .catch function to promise.
Does anyone know what is wrong here?
Thanks for help!
A superagent request already returns a promise so use that instead
const request = (url) => superagent.get(url).then(res=> {/*transform res**/ return res})
The code you provided does not have that behavior but I am familiar with the uncaught in promise warning.
If you want to catch an error of a promise later you have to catch it first and return the promise before you catch it.
The following will give you uncaught in promise warning:
var failPromise = val => Promise.reject(val);
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
But if you change it a bit then you won't
var failPromise = val => {
const p = Promise.reject(val);
p.catch(ignore=>ignore);
return p;//return the promise that did not have the catch
};
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
As suggested before; your request method should just return the promise that superagent returns but in such a way that you can catch the reject later without the errors(warnings):
const request = (url) => {
const p = superagent
.get(url);
p.catch(ignore=>ignore);
return p;
})
If you are wondering why you get uncaught in promise; it is because you catch the rejection in the queue and not in the stack. Stack and queue is explained here.
The example that catches in stack causes no errors in the console but then returns the promise that did not have the catch on it. Depending how far in the queue you are going to catch it it may still give you errors but in code provided it won't.

jest jumps out of function with promise

I'm trying to test a function which calls another module's function which returns a promise,
The problem is that jest does not wait for completion of the myFunction and jumps out of it and treat it as a promise, as result section shows the "done" message is printed before "resolve" message. I have work around using setImmediate but I rather not to use it and want to understand the reason.
the simplified version of the code is following:
The module which is mocked
// repo.js
const getItems = () => {
console.log('real');
return new Promise((resolve, reject) => {
setTimeout(
() => resolve('result'), 1000);
}
);
}
module.exports = {
getItems
};
Unit under test:
// sample.js
const repo = require('./repo');
const myFunction = (req, res) => {
console.log('myFunction');
repo.getItems()
.then(goals => {
console.log('resolve');
res.val = 'OK';
}).catch(err => {
console.log('reject');
res.val = 'Failed';
});
return;
};
module.exports = {myFunction};
Test file:
// sample.test.js
const repo = require('./repo');
const sample = require('./sample');
const result = {
'message': 'done'
};
describe('Tests for receiving items', () => {
it('should call and be successful. ', () => {
repo.getItems = jest.fn(() => {
console.log('mocking');
return new Promise((resolve) => ( resolve(result) ));
});
const response = {val: 'test'};
const request = {};
sample.myFunction(request, response);
console.log('done');
expect(response.val).toBe('OK');
})
}
);
The result is:
console.log MySample\sample.js:5
myFunction
console.log MySample\sampel.test.js:11
mocking
console.log MySample\sampel.test.js:17
done
console.log MySample\sample.js:9
resolve
Error: expect(received).toBe(expected)
Expected value to be (using ===):
"OK"
Received:
"test"
Expected :OK
Actual :test
The test you wrote reflects the correct usage, and you might say it fulfilled its purpose, because it uncovered a bug in your implementation.
To show what exactly went wrong, I will get rid of everything that is not needed, which leads to an even more minimal example. The following test file can be run by Jest and it reproduces your problem.
const myFunction = (res) => {
Promise.resolve()
.then(goals => {
res.val = 'OK';
}).catch(err => {
res.val = 'Failed';
});
return;
};
it('should call and be successful. ', () => {
const response = {val: 'test'};
myFunction(response);
expect(response.val).toBe('OK');
})
myFunction starts a promise (which resolves immediately here with no value) and returns nothing (undefined). You can also test the error part by using Promise.reject instead of Promise.resolve. When you call myFunction(response) the next line is executed when myFunction finishes. This is not when the promise actually finishes, but the function itself. The promise could take any amount of time and there is no way for you tell when it actually finished.
To be able to know when the promise finished, you need to return it, so you can use a .then() on it to execute something after the promise has been resolved. Both .then() and .catch() return a new promise which resolves with the returned value, which in this case is again undefined. That means you need to do your assertion in the .then() callback. Similarly, Jest thinks that the test ends as you exit the function even though it should wait for the promise to be settled. To achieve this you can return the promise from the test and Jest will wait for its completion.
const myFunction = (res) => {
// Return the promise from the function, so whoever calls myFunction can
// wait for the promise to finish.
return Promise.resolve()
.then(goals => {
res.val = 'OK';
}).catch(err => {
res.val = 'Failed';
});
};
it('should call and be successful. ', () => {
const response = {val: 'test'};
// Return the promise, so Jest waits for its completion.
return myFunction(response).then(() => {
expect(response.val).toBe('OK');
});
})
You can also use async/await, but keep in mind that you still need to understand how promises work, as it uses promises underneath. An async function always returns a promise, so Jest knows to wait for its completion.
it('async/await version', async () => {
const response = {val: 'test'};
// Wait for the promise to finish
await myFunction(response);
expect(response.val).toBe('OK');
})
Usually you would also return a value from the promise (in .then() or .catch()) instead of mutating an outer variable (res). Because if you use the same res for multiple promises, you will have a data race and the outcome depends on which promises finished first, unless you run them in sequence.

Categories