Promise test case failing only for Node 16 - javascript

We are currently upgrading from Node v12 to Node v16.
After the node version update, the following test is failing with this error.
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch(). The promise rejected with
the reason "Error".] { code: 'ERR_UNHANDLED_REJECTION'
But why does a node version cause this.
This error stops if I remove the .catch() from the test case.
But it is still incorrect outcome cos the snapshot no longer matches if I do that.
This is the test.
it('should test', async () => {
const thePromise = Promise.resolve({
json: () => {
Promise.reject(new Error());
},
});
// validateData is fetch call.
validateData.mockReturnValueOnce(thePromise);
const rendered = renderModule({ state: { code: 'mockRefCode' } });
rendered.find('#btn').simulate('click');
await ((thePromise)).then().catch(); // If i remove this catch, above error gone but wrong snapshot (null snapshot).
expect(rendered.find('#error')).toMatchSnapshot();
});
This is the code being tested.
export const validateCode = async (env, code) => fetch(`www.getapiresponse.example`);
export const SomeReactComponent(props) {
…
const [validatingCode, setValidatingCode] = useState(false);
const [validationError, setValidationError] = useState('');
const validateData = () => {
if (!code) {
setValidationError(langPack.emptyField);
return;
}
setValidatingCode(true);
validateCode(environment, code.trim())
.then((response) => response.json())
.then((body) => {
if (body.response_status_code !== '200') {
// some logging
} else {
setValidationError('');
}
})
.catch(() => setValidationError(langPack.serviceError))
.finally(() => setValidatingCode(false));
};
…
}
If I remove the .catch(), the snapshot ends up as null.
The correct snapshot should be something like following:
- <div
- className="error"
- id="error"
- >
- service
- </div>

Related

Testing an asynchronous throw error in a Promise catch with Jest

I have the following code that I'd like to test.
const Component: React.FC = () => {
const handleSubmit = (action) => {
doSomethingAsynchronous()
.then(() => /* something on success */)
.catch((err) => {
// Display the error message
action();
// Rethrow the exception so it can be handled up the chain
throw err;
})
}
return <Form onSubmit={handleSubmit} />;
}
This code performs a simple asynchronous action that fails or resolves. On a failure, the component is re-rendered to show an error message, and the error is rethrown to log to the console/our logging system and for parent components to deal with.
The problem comes when I am attempting to test the error handling behaviour to ensure that the error messages are being set. Simple testing such as:
describe('Component', () => {
it('handles an error', async () => {
// Setup
const mockAction = jest.fn();
const render = shallowRender(<Component />);
submissionHandler = render.find(Component).invoke('onSubmit');
// Act
submissionHandler(mockAction);
await () => new Promise(setImmediate); // To wait for promise execution
// Assert
expect(mockAction).toHaveBeenCalled();
})
})
Results in Jest failing the test as an error has been thrown in the test by the component, inside the catch block (as expected). However, my attempts to suppress this also result in the same error being thrown and failing the test.
try {
// Act
submissionHandler(mockAction);
await () => new Promise(setImmediate); // To wait for promise execution
} catch (e) {}
I also tried using expects().toThrow(), but this instead returns the jest error Received function did not throw. I assume this is because due to the promise the execution is no longer in the same function scope, so isn't being recognised by Jest as originating from that function?
await expect(async () => {
submissionHandler(mockAction);
await () => new Promise(setImmediate);
}).toThrow();
Does anyone know the best way to test this? I'm aware I can cheat by making onSubmit return my promise here and catching the exception there, but I'd avoid doing that to stop my function returning for testing purposes.
You need to unpack your errors from your promise with .rejects
try this:
import { spyOn } from 'jest-mock';
...
it("should error", async() => {
spyOn(console, 'error'); #removes error from output
await expect( yourAsyncMethod() ).rejects.toThrow() # .rejects unpacks errors from promises
}

Writing tests for an async function with Jest

I am trying to test the following function:
import * as config from "./config.js";
export const state = {
recipe: {},
};
export async function loadRecipe(id) {
let result;
let data;
try {
result = await fetch(`${config.API_URL}/${id}`);
data = await result.json();
} catch (e) {
console.log(e);
}
console.log(result.status);
if (!result.status === 200) {
console.log("here");
throw new Error(`${data.message} (${result.status})`);
console.log("1here");
}
const { recipe } = data.data;
state.recipe = {
id: recipe.id,
title: recipe.title,
publisher: recipe.publisher,
sourceUrl: recipe.source_url,
image: recipe.image_url,
servings: recipe.servings,
cookingTime: recipe.cooking_time,
ingredients: recipe.ingredients,
};
}
Here are the tests I have written. I am using jest-fetch-mock to mock the global fetch function. If I comment-out the second test and run it, I get the expected results. Now I want to test if a bad id is entered. So I created a second test with bad data and am mocking the result from the API:
"use strict()";
import * as model from "../model.js";
import * as apiResponse from "../__fixtures__/apiResponse.js";
import * as recipes from "../__fixtures__/recipes.js";
beforeEach(() => {
fetch.resetMocks();
});
describe("Request from the api", () => {
test("Received valid data", async () => {
fetch.mockResponseOnce(
JSON.stringify(apiResponse.id_5ed6604591c37cdc054bca85)
);
const res = await model.loadRecipe("5ed6604591c37cdc054bca85");
expect(model.state.recipe).toStrictEqual(
recipes.recipe_5ed6604591c37cdc054bca85
);
expect(fetch).toHaveBeenCalledTimes(1);
});
test("Requested an invalid id", () => {
const body = apiResponse.invalid_5ed6604591c37cdc054bca85zzzzz;
const init = { status: 400, statusText: "Bad Request" };
fetch.mockResponseOnce(JSON.stringify(body), init);
expect(async () => {
await model.loadRecipe("5ed6604591c37cdc054bca85zzzzz");
}).toThrowError();
expect(fetch).toHaveBeenCalledTimes(1);
});
});
Whenever the second test is run I get the following error from yarn:
RUNS src/js/__tests__/model.test.js
node:internal/process/promises:225
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "TypeError: Cannot destructure property 'recipe' of '((cov_24wkscmv5p(...).s[10]++) , data.data)' as it is undefined.".] {
code: 'ERR_UNHANDLED_REJECTION'
}
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Please help me understand what is causing the issue.
Finally! I got it to work. I had to add rejects to catch the error. I got it from this page: https://eloquentcode.com/expect-a-function-to-throw-an-exception-in-jest
test("Requested an invalid id", () => {
const body = apiResponse.invalid_5ed6604591c37cdc054bca85zzzzz;
const init = { status: 400, statusText: "Bad Request" };
fetch.mockResponseOnce(JSON.stringify(body), init);
expect(async () => {
await model.loadRecipe("5ed6604591c37cdc054bca85zzzzz");
}).rejects.toThrowError();
expect(fetch).toHaveBeenCalledTimes(1);
});
Basically that error is due to your reject block/case.
So, when using async await, you could better keep it inside the try catch block to capture the reject case.
Consider following snippet for example -
var prom = function(p_param) {
return new Promise((resolve,reject) => {
setTimeout(()=>{
if(p_param%2 == 0){
resolve('Data good');
} else {
reject('Bad Data');
}
}, 3000);
});
}
async function runMain(p_data){
console.log('Verifying Data - '+p_data);
try {
var t = await prom(p_data); //resolve
console.log(t);
} catch(err) {
console.log(err); //reject
}
}
runMain(5);
This snippet would result in 'reject', Output:
Verifying Data - 5
Bad Data

How to test status of a promise inside Promise.finally() without awaiting it in production code

I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.
However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected.
edit: But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).
How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?
E.g. if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:
https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js
test("finally", async () => {
const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
const newState = actions.reduce(reducer, undefined);
expect(newState).toEqual({});
});
const dispatchMock = async thunk => {...};
// ----- simplified "production" code -----
const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
dispatch(get(parentId)); // tests pass if the promise here is rejected
}
};
const get = id => async dispatch => {
dispatch("get start");
try {
await someFetch(id);
dispatch("get success");
} catch (e) {
dispatch("get failed");
throw e;
}
};
const someFetch = async id => {
if (id === "forgottenParent") {
throw new Error("imagine I forgot to mock this request");
}
Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here
There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.
You might be looking for
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
await dispatch(get(parentId));
// ^^^^^
}
};
Notice that throwing exceptions from a finally block is not exactly a best practice though.
edit: more general solutions available on https://stackoverflow.com/a/58634792/1176601
It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, e.g.:
export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally
const add = parentId => async dispatch => {
...
} finally {
// not awaited here because I don't want to change the current Promise
_promiseFromFinally = dispatch(get(parentId));
}
};
and update the test to await the test-only Promise:
test("finally", async () => {
...
// but I want to fail the test if the Promise from finally is rejected
await _getPromiseFromFinallyInTests()
});

Multiple awaits inside try catch not catching error

I am working on a project that involves updating the entire mongoDB collection everyday.
I am making multiple asynchronous calls so I decide to use async/await and try/catch. My code looks like this:
const updateMongoData = async () => {
try {
const data = await getData(); //This is the new data that I am using to update Mongo docs
const countries = await GlobalData.find();
countries.forEach(async (row) => {
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Date.now(),
}
);
});
} catch (err) {
console.log(err);
}
};
Everything works fine except if I make a syntax error e.g Dated.now() instead of Date.now(). This is will give my an error saying
(node:8248) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 368)
I tried inserting another try catch instead my forEach and moved the forEach inside and this seems to catch the syntax error. Why does this work? And what is the cleanest way to do this?
GlobalData.find()
.then(countries => {
countries.forEach(async (row) => {
try{
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Date.now(),
}
);
}
catch(err){
console.log(err);
}
})
}).catch(err => console.log(err));
Your try catch is unable to catch the ReferenceError thrown by changing Date to Dated because catch requires a call to the implicit async/await Promise chain's reject handler. Errors thrown inside Array.prototype.forEach will not see that handler.
You can think about Array.prototype.forEach as a method that starts off a whole slew of runaway executions; none of which are meant to be awaited from the parent scope
You can use Promise.all and Array.prototype.map to catch the error instead.
const updateMongoData = async () => {
try {
const data = await getData(); //This is the new data that I am using to update Mongo docs
const countries = await GlobalData.find();
await Promise.all(countries.map(async (row) => {
const res = await Global.updateOne(
{ country: row.country },
{
//Use data
lastUpdated: Dated.now(),
}
);
}))
} catch (err) {
console.log('yo', err);
}
};
updateMongoData() // > yo ReferenceError: Dated is not defined
Your second example works because the try catch block is sharing the same scope as the Array.prototype.forEach block. If you moved that try catch statement outside, however, it will not see the error.
There is another way using a project I started. Contributions are welcome.
const { pipe, fork, map, tryCatch } = require('rubico')
const updateGlobalByCountry = (country, data) => Global.updateOne(
{ country },
{ lastUpdated: Date.now() }, // <- use data
)
const updateMongoData = tryCatch(
pipe([
fork({
data: getData, // () => data
countries: GlobalData.find, // () => countries
}), // () => ({ data, countries })
({ data, countries }) => map(
country => updateGlobalByCountry(country, data)
)(countries), // ({ data, countries }) => updatedCountries
]),
err => console.log(err),
)

Get data using await async without try catch

I am trying to use await-async without try-catch for this:
const getUsers = async (reject, time) => (
new Promise((resolve, reject) => {
setTimeout(() => {
if (reject) {
reject(....)
}
resolve(.....);
}, time);
})
);
module.exports = {
getUsers ,
};
With try-catch block it looks like this:
const { getUsers } = require('./users');
const users = async () => {
try {
const value = await getUsers(1000, false);
.....
} catch (error) {
.....
}
}
users();
How can I write the same code without using the try-catch block?
Using the promise functions then-catch to make the process simpler I use this utils :
// utils.js
const utils = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
module.exports = utils;
And then
const { getUsers } = require('./api');
const utils = require('./utils');
const users = async () => {
const { error, data } = await utils(getUsers(2000, false));
if (!error) {
console.info(data);
return;
}
console.error(error);
}
users();
Without using the try-catch block I got the same output, this way makes it better to understand the code.
In Extension to L Y E S - C H I O U K H's Answer:
The Utils Function is actually correct but, make sure to add the return keyword before the promise as shown down below:
// utils.js
const utils = promise => (
return promise
.then(data => { [data, null]; })
.catch(error => { [null, error]; });
);
module.exports = utils;
When Calling in Main Code:
let resonse, error; // any variable name is fine make sure there is one for error and the response
[response, error] = await utils(any_function()); // Make sure that inside the tuple, response is first and error is last like: [response, error].
if (error) console.log(error);
// -- Do Whatever with the Response -- //
Using My Method Would Give you Benefits like:
Your Own Variable Names.
Not Running into Type Safety issues when using Typescript.
Good Reason to Strong Type your code.
Personally, I have been using this in my code lately, and has reduced some many headaches, my code is cleaner, I don't have to stick with the same variable names, especially when working on a large codebase.
Happy Coding :)
See Ya!
If you have a valid default for the error case you can use the catch method on the getUsers promise and then await a promise whose error will be handled
const users = async () => {
const value = await getUsers(1000, false).catch(e => null);
}
While this approach should work it should be noted that this may mask the case when getUsers returns null vs when it raises an error, and you will still need to check for the null or get a null access error. All in all I would stick with the try { .. } catch (e) { ... } for most casses
A package I found called await-to-js can also help it.
import to from 'await-to-js';
const [err, users] = await to(getUsers());
if(err) doSomething();
The idea is like Lyes CHIOUKH's method, just a wrapper. Copied the source code here.
/**
* #param { Promise } promise
* #param { Object= } errorExt - Additional Information you can pass to the err object
* #return { Promise }
*/
export function to<T, U = Error> (
promise: Promise<T>,
errorExt?: object
): Promise<[U | null, T | undefined]> {
return promise
.then<[null, T]>((data: T) => [null, data])
.catch<[U, undefined]>((err: U) => {
if (errorExt) {
Object.assign(err, errorExt);
}
return [err, undefined];
});
}
export default to;
If you have such above single line async/await function, then this would have been clean code for you:
const getUsers = async (time, shouldReject=false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(Error('Rejected...'));
} else {
resolve(["User1", "User2"]);
}
}, time);
});
}
const userOperation = users => {
console.log("Operating user", users);
}
// Example 1, pass
getUsers(100)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
// Example 2, rejected
getUsers(100, true)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
And for multiple await in a single async function, it would good to have try/catch block as below:
const users = async () => {
try {
const value = await getUsers(1000, false);
const value1 = await getUsers2(1000, false);
...
} catch (error) {
...
}
}

Categories

Resources