`co` exception causes Node Promise `reject()` to throw in Jetbrains debugger - node.js

I run fairly complex piece of code in Node 6.x, and I'm unable to replicate the problem in simple conditions. The gist of the relevant part of the app is
// app.js
someCoWrapper(function*() {
yield hapiServerStart();
// unreachable
...
})
.catch(console.error);
// some-co-wrapper.js
function someCoWrapper(genFn) {
return co(genFn).then(
(result) => new Promise((resolve) => resolve(result)),
(error) => {
return new Promise((resolve, reject) => {
return reject(error)
})
}
)
}
// some-callback-called-inside-serverStart.js
...
assert(..., 'I was thrown from server init callback');
...
The wrapper for co was there to switch between promise implementations, and it was meant to be transparent (it failed).
Here I expect co promise to catch an exception from generator and log it to console.error. This works as expected with node debug app.js. In Jetbrains (PhpStorm 2016), it works with Run. But it throws an uncaught exception with Debug, call stack is really obscure:
The problem doesn't occur when yield hapiServerStart(); is secured with try...catch (naturally).
Changing Promise implementation helps, both
global.Promise = require('es6-promise').Promise;
and
global.Promise = require('bluebird');
work. And the most interesting thing is that changing the code above to
return co(genFn)
.then(
(result) => Promise.resolve(result)),
(error) => Promise.reject(error))
);
helps, too!
Too many moving parts are there, and the fact that it occurs in Jetbrains product suggests that there may be something fishy with it.
I would be grateful for any comments that may shed some light on what happens here.
And the real quesion is how new Promise((err) => reject(err)) differs from Promise.reject(err) in Node/V8 implementation? Is there something that may cause it to misbehave on some conditions (not just in debugger but in production)?

Related

Best way to test rollback after promise rejection?

I often have situations where the behavior I'm trying to achieve is like this:
User takes action
Website updates UI optimistically
Website fires update to server
Website awaits server response
If the update fails, website rolls back UI change
I've often found myself adding multiple timeouts to my tests to try to both assert that the optimistic update was made and then gets rolled back after rejection. So something like this:
it('rolls back optimistic update', async () => {
jest.mocked(doUpdate).mockReturnValue(new Promise((resolve, reject) => {
setTimeout(reject, 1000);
});
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
await screen.findByText('not done')
});
But this has some pretty big downsides:
Setting up tests like this is fidgety and difficult.
Using timeouts inside my tests results in tests that are ensured to be slower than they need to be.
Any significant changes to the test environment or tools have a high chance of breaking these tests.
Tests get even slower and more complicated once I need to test things like ensuring that subsequent user actions don't clobber previous actions.
If the promise happens to resolve after the test ends, I often end up with React complaining about updates not being wrapped in act.
How can I test this type of behavior in a way that is as efficient as possible and robust against test environment changes?
I now use this helper function in situations like this, where I want to control the precise order of events and promises are involved:
export default function loadControlledPromise(mock: any) {
let resolve: (value?: unknown) => void = () => {
throw new Error('called resolve before definition');
};
let reject: (value?: unknown) => void = () => {
throw new Error('called reject before definition');
};
const response = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
jest.mocked(mock).mockReturnValue(response);
return { resolve, reject };
}
Using this helper function, the example test becomes:
it('rolls back optimistic update', async () => {
const { reject } = loadControlledPromise(doUpdate);
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
reject();
await screen.findByText('not done')
});
This ensures:
The test doesn't spend more time waiting than necessary.
Changes in the test environment are less likely to break the test.
More-complicated sequences of events can be tested without resulting in incomprehensible test code.
I can force promises to resolve before the test ends. even when I need to test conditions prior to the promise resolving, helping to avoid React complaining about updates happening outside of act.

Unhandled Promise Rejection: AxiosError: Network Error

Working on a MERN stack project. When trying to access the site on localhost on iPhone Posts are posts are not loading. Working fine on adnroid devices.
Screenshot of error
const fetchFeedPosts = async () => {
const URL = `${BASE__URL}api/posts/feed/${LOGGED__IN__USER}`;
await axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((e) => console.log(e.response));
}
fetchFeedPosts()
What the error means
When an Error is thrown (e.g. throw new Error()) it can
be catched locally (e.g. try{...} catch(err) { /** here */ })
or be passed on to the calling function. And in that fashion it bubbles up from caller to caller until it reaches a catch somewhere.
However, if it continues to bubble up, and there's nothing that captures it by the time it reaches the root of your application or code, then that will result in a so-called "Unhandled" error.
Now, as you may know, promises are more like jobs that drift around. They aren't called from the root of your application. But as they bubble up they can also reach a similar root-point, in which case they become "Unhandled Promise rejections".
What to do about it
Unhandled errors or rejections are bad practice though. Errors should be caught somewhere. And without catching them, you can't really know what has caused the error to happen in the first place.
In most cases, you can catch them with a .catch() function, (e.g. yourPromise.catch((err) => {console.err(err)}))
In case your promise is handled in an async function and waited for with an await keyword, then it's slightly different. In that case it makes more sense to use a try-catch block to capture your error.
How to apply it to your code
So, the first way of doing it would be to use the .catch() function
axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((err) => console.error(err));
The alternative is to use the await syntax with a try-catch. If you want to use this syntax, you have to put the async keyword before your function.
try {
const response = await axios.get(URL)
setFeedPosts([...response.data].reverse()))
} catch (err) {
console.log(err);
}
Sure, you could mix the 2, but in most cases that would be rather strange.

Jest Cannot read property 'createEvent' of null

I was trying to mock rejected value and got this error. It's weird that this construction works in the case of "success" addUser.mockImplementation(value => jest.fn().mockResolvedValue(value)), but when I'm trying to do the same trick with rejecting, it doesn't work and says 'Cannot read property 'createEvent' of null'
Here is my test case
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
const enqueueSnackbar = jest.fn()
useSnackbar.mockReturnValue({ enqueueSnackbar })
const { emailInput, form, submitButton } = setup()
await act(async () => {
fillIn(emailInput, 'sample#mail.com')
})
expect(emailInput.value).toBe('sample#mail.com')
expect(submitButton).toHaveProperty('disabled', false)
await act(async () => {
fireEvent.submit(form)
})
expect(enqueueSnackbar).toHaveBeenCalledTimes(1)
expect(enqueueSnackbar).toHaveBeenCalledWith(`Sample error`, {
variant: 'error'
})})
Does anyone know how to make it work?
This seems to be the #1 question that is found when someone Googles "Cannot read property 'createEvent' of null", so leaving this answer here for those readers:
For me this error came in the midst of a test.
When executing a series of tests, some test or the other used to fail with this error, with no indication of where things went wrong. But the answer turned out to be not the test but the component itself:
It was an unmocked API call.
There was an API call being made in a hook and that hook was used in the component with the failing tests. Obviously Jest cleaned up everything after completing its test, and when the call returned, it found nothing so it errored out.
Mocking the hook solved the issue.
If someone comes across such an error, make sure to mock any asynchronous logic you have, especially if it interacts with the DOM when it returns.
Similar to what #alexandre_anicio stated. I was getting this error when using the findAllByText query.
expect(screen.findAllByText('word'))...
When I switched to the getAllByText the error went away and the test passed.
expect(screen.getAllByText('word'))...
If I used expect(await screen.findAllByText('word'))... I noticed the test passed as well.
Digging deeper, this is because findBy tests return a promise so the await is needed. https://testing-library.com/docs/guide-disappearance/#1-using-findby-queries
It would have been nice for the library to throw a better error however.
This seems to work for me but I can't explain it. Try removing your act() wrapper, and use await immediately after calling the fireEvent function.
fireEvent.submit(form);
await wait();
When I encountered this same error message, I discovered I had forgotten to declare my test function as async after I updated the expectation to include await.
waitFor already uses act under the hood so there's no need to use the act blocks there.
I recognize the error you mentioned but the way I replicate it is using waitFor without await, something like this:
it('works', async() => {
render(<SomeComponent />);
// (some setup here)
waitFor(() => { // notice that we are not awaiting this promise
expect(someChange).toBeInTheDocument();
});
});
Could you try
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
const enqueueSnackbar = jest.fn()
useSnackbar.mockReturnValue({ enqueueSnackbar })
const { emailInput, form, submitButton } = setup()
fillIn(emailInput, 'sample#mail.com') // This is using some fireEvent under the hood right?
await waitFor(() => {
expect(emailInput.value).toBe('sample#mail.com')
expect(submitButton).toHaveProperty('disabled', false)
});
fireEvent.submit(form)
await waitFor(() => {
expect(enqueueSnackbar).toHaveBeenCalledTimes(1)
expect(enqueueSnackbar).toHaveBeenCalledWith(`Sample error`, {
variant: 'error'
})
});
})
Similar issue and error messages, adding await before userEvent did the trick
Before
userEvent.upload(screen.getByRole('button'), ...)
userEvent.upload(screen.getByLabelText('Upload'), FILE)
After
await userEvent.upload(screen.getByRole('button'), ...)
await userEvent.upload(screen.getByLabelText('Upload'), FILE)
I had some problems using mockImplementation(() => Promise) (returning some promise) and the await waitFor(()=> ...) at the same time.
If you are using react-testing-library, you can work around this problem using findBy query, that are a combination of getBy queries and waitFor. The only downside is that you have to find something visual (a text, data-test-id, label, etc...) that can tell you that the mock function have been called. On your code you can try something like this:
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
await screen.findByText('Sample Error message reflected in your component')
... rest of your tests ...
})
1. await waitFor(() => expect(history.location.pathname).toBe('/auth'))
2. await waitFor(() => expect(history.location.pathname)).toBe('/auth')
It's about something else but the same error. Spent 2 hours so you don't have to :)
The second one with the parenthesis in the wrong place was the culprit
I was getting
/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:3905
var evt = document.createEvent('Event');
^
TypeError: Cannot read property 'createEvent' of null
at Object.invokeGuardedCallbackDev (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:3905:26)
at invokeGuardedCallback (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:4056:31)
at flushPassiveEffectsImpl (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:23543:11)
at unstable_runWithPriority (/…/node_modules/.pnpm/scheduler#0.20.2/node_modules/scheduler/cjs/scheduler.development.js:468:12)
at runWithPriority$1 (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:11276:10)
at flushPassiveEffects (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:23447:14)
at Object.<anonymous>.flushWork (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom-test-utils.development.js:992:10)
at Immediate.<anonymous> (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom-test-utils.development.js:1003:11)
at processImmediate (internal/timers.js:461:21)
Tracked down to an import statement.
I was able to "fix" it by changing:
import { AddCircle, RemoveCircle } from '#mui/icons-material';
to
import AddCircle from '#mui/icons-material/AddCircle';
import RemoveCircle from '#mui/icons-material/RemoveCircle';
Crazy.
If the error is because of wrong usage of jest's findBy* instead of async/await you can also return promise:
it('test', () => {
expect.assertions(1);
return screen
.findByTestId('iiError')
.then(elem =>
expect(elem).toHaveTextContent(
"This is error message"
)
);
});
Do not forget about expect.assertions and return!
Ref: https://jestjs.io/docs/tutorial-async
I had the same issue, the culprit in my case was that the rendered React component was unmounted with .unmount(). A running API call triggered a callback and React tried to update the DOM, that was already unmounted.
Since this is the top result on Google for this issue, I'll add my own answer. For me, this issue was happening on Circle CI when my tests were running. The Circle CI server ran the tests more slowly because it's CPU is limited, so a long-running test with lots of userEvent.types in it was exceeding the default timeout of 5 seconds.
I don't know why it didn't give an error about exceeding the timeout, but this thread helped me track it down.
All I had to do was increase the timeout on the long-running test.
Error occurred for me because I had work scheduled from useEffect that resolved after the rest was torn down.
The solution is to await Promise.sleep() after each test.
I was facing the same issue
It had to something with the async function not completing before the test case completes
I solved this using await flushMicrotasksQueue()
in my code

What am I doing wrong with this async get request?

I am struggling to figure out what the error is in this async function. I keep getting this error message:
"Unexpected token catch"
and if that error is fixed I get this error Message:
"UnhandledPromiseRejectionWarning"
"DeprecationWarning"
router.get('/Views', async (req, res) => {
console.log("Google auth ", googleAuth)
const organizationId = req.verifiedToken.OrganizationId;
console.log("Got here")
const url = myUrl;
try{
const client = await auth.getClient({
scopes: [
'https://www.googleapis.com/auth/analytics.readonly'
]
})catch(err){
console.log(err);
throw err;
};
const outcome = await client.request({ url })catch(err){
console.log(err);
throw err;
};
};
console.log("Successfully connected!", organizationId);
return responses.success(res, outcome.data);
});
The line
const outcome = await client.request({ url })catch(err){
introduces a catch exception handler without a prior try block. You appear to have caught (no pun intended) this syntax error (though you haven't detailed the code changes to get rid of it).
Unfortunately you haven't posted a self-contained code fragment and you haven't specified which framework/libraries you use on top of node.js. If you are using the Express framework and an ajax library, it might be that your try block is missing an exception handler and the catch statements are meant to be method calls:
router.get('/Views', async (req, res) => {
console.log("Google auth ", googleAuth)
const organizationId = req.verifiedToken.OrganizationId;
console.log("Got here")
const url = myUrl;
try{
const client = await auth.getClient({
scopes: [
'https://www.googleapis.com/auth/analytics.readonly'
]
})
.catch(err){ //*** syntax error in your code sample
console.log(err);
throw err;
};
const outcome = await client.request({
url
})
.catch(err){ //*** syntax error in your code sample
console.log(err);
throw err;
};
} catch (e) { //*** exception handler missing from your original code missing
// exception handling code, possibly empty; there are the .catch calls after all
}
console.log("Successfully connected!", organizationId);
return responses
.success ( res, outcome.data )
.error ( )
//*** This call is missing from your code.
// It is assumed that 'responses' holds a "Promise", see explanation
;
});
A 'Promise' in asynchronous programming is an abstraction of a value not yet known (think of it as a placeholder for that value). There are 2 basic possible scenarios: either that value will eventually be computed (#1) or it is ascertained that it will never be computed at all (#2). A promise library handles these scenarios. The reference API sports promise objects with a .then ( fn_ok, fn_fail ) method taking 2 functions as arguments, one being associated the first scenario, one with the second. As soon as a scenario is established, the respective function will be called. Promise libraries may add additional layers of abstraction, possibly producing the .success/.error calls from the code sample. Note that you promise libraries do usually support 'chaining': In the code sample, the call to .success (.error) would actually make sure that res and outcome.data (nothing) will be preserved and fed to the handler for the scenario #1 (#2) and would return a promise (technically the same object with some properties being redefined).
The second error you have received ( UnhandledPromiseRejectionWarning ) would thus stem from not handling scenario #2. However, the case that the future computation of a value will fail is a very possible outcome and should be accounted for in general. Otherwise your code is amenable to run-time errors that are very hard to track down as you will neither be notified of the code section the error occurs nor (due to async programming) will you have guarantees on the execution order of code fragments. Moreover this coding style easily rsults in a deadlock or the eventual exhaustion of some resource (memory, file handles, ...)
This explanation is deliberately written following a (hopefully) intuitive approach and is technically somewhat sloppy. For a detailed explanation consult https://www.promisejs.org/, MDN: Promises, or similar resources.
Caveat
There is some guesswork involved in this answer. However the general outline of the problem's origin should remain valid.

NodeJS: Promise won't catch thrown exceptions

I have a code like this (simplified):
getStreamFor(path) {
// both, remote and local, return a Promise
if(...) { return getRemoteFileStream(path); }
else { return getLocalFileStream(path); }
}
getRemoteFileStream(path) {
// should throw in my case (MyError)
const validPath = validatePath(path);
return readStreamIfValid(validPath);
}
and in the test case:
it('should throw MyError', () => {
return getStreamFor(path)
.then(() => {})
.catch(error => expect(error).to.be.instanceOf(MyError));
});
The problem is, that when the validatePath(path) Method throws (due to invalid path), nothing get caught in the test case promise. The output in my terminal / console is a regular exception as if it was uncaught.
Does anybody have an idea, why the the Promise wouldn't recognize the throw? How can I fix it without probably surrounding the call in the test case with another "try catch" (since the promise should do that for me)?
Maybe there is a general best practise how to structure Promises in order to avoid error swallowings like these?
Thanks for your help!
The problem here is that validatePath() is not part of the promise chain returned by getRemoteFileStream()
One possible solution is the following:
getRemoteFileStream(path) {
return Promise.resolve()
.then(() => validatePath(path))
.then(validPath => readStreamIfValid(validPath));
}
An exception thrown by validatePath() would now be handled in the Promise's catch handler.

Resources