Jest Cannot read property 'createEvent' of null - jestjs

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

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.

Selenium Test Case not getting executed with Jest

I am working on setting up an automation test suite for an application using selenium and jest and it turns out that the console never reaches the inner body of the minimalist test case written below.
describe('When a user Opens Launchpad', () => {
test('It should be able to Navigate to Tasks Application without errors', async () => {
driver.get('http://localhost:4004/fiori.html').then(function () {
const temp = driver.findElement(By.xpath("li[#id='__tile11']"));
temp.then(function (element){
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
})
});
}, 200000);
});
I looked online and tried multiple fixes like putting the driver.get() method above all these functions, making the test cases synchronous and using getText() instead of getAttribute() but none of them worked.
I either get an element not found error (The element actually exists when I check it on the chromium browser) or the test case executes successfully without reaching the expect statement in debug mode.
Bottomline is that driver.findElement() returns a promise instead of an element and would be great if I could get an element instead of promise.
Any help or correction here would be greatly appreciated.
If the function is async you should return the promise chain from that function or just use await keyword. So try following:
test('It should be able to Navigate to Tasks Application without errors', async () => {
await driver.get('http://localhost:4004/fiori.html');
const temp = await driver.findElement(By.xpath("li[#id='__tile11']"));
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
}, 200000);
or
test('It should be able to Navigate to Tasks Application without errors', async () => {
return driver.get('http://localhost:4004/fiori.html').then(function () {
const temp = driver.findElement(By.xpath("li[#id='__tile11']"));
temp.then(function (element){
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
})
});
}, 200000);

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.

How can I test that a promise have been waited for (and not just created) using Sinon?

Let's say I have a function:
const someAction = async(): Promise<string> => {
/* do stuff */
};
And I have some code that just needs to run this action, ignoring the result. But I have a bug - I don't want for action to complete:
someAction();
Which, instead, should've been looked like this:
await someAction();
Now, I can check that this action was ran:
const actionStub = sinon.stub(someAction);
expect(actionStub).to.have.been.calledWith();
But what's the most concise way to check that this promise have been waited on?
I understand how to implement this myself, but I suspect it must have already been implemented in sinon or sinon-chai, I just can't find anything.
I can certainly say that nothing like this exists in sinon or sinon-chai.
This is a difficulty inherent to testing any promise-based function where the result isn't used. If the result is used, you know the promise has to be resolved before proceeding with said result. If it is not, things get more complex and kind of outside of the scope of what sinon can do for you with a simple stub.
A naive approach is to stub the action with a fake that sets some variable (local to your test) to track the status. Like so:
let actionComplete = false;
const actionStub = sinon.stub(someAction).callsFake(() => {
return new Promise((resolve) => {
setImmediate(() => {
actionComplete = true;
resolve();
});
});
});
expect(actionStub).to.have.been.calledWith();
expect(actionComplete).to.be.true;
Of course, the problem here is that awaiting any promise, not necessarily this particular one, will pass this test, since the variable will get set on the next step of the event loop, regardless of what caused you to wait for that next step.
For example, you could make this pass with something like this in your code under test:
someAction();
await new Promise((resolve) => {
setImmediate(() => resolve());
});
A more robust approach will be to create two separate tests. One where the promise resolves, and one where the promise rejects. You can test to make sure the rejection causes the containing function to reject with the same error, which would not be possible if that specific promise was not awaited.
const actionStub = sinon.stub(someAction).resolves();
// In one test
expect(actionStub).to.have.been.calledWith();
// In another test
const actionError = new Error('omg bad error');
actionStub.rejects(actionError);
// Assuming your test framework supports returning promises from tests.
return functionUnderTest()
.then(() => {
throw new Error('Promise should have rejected');
}, (err) => {
expect(err).to.equal(actionError);
});
Some assertion libraries and extensions (maybe chai-as-promised) may have a way of cleaning up that use of de-sugared promises there. I didn't want to assume too much about the tools you're using and just tried to make sure the idea gets across.

Why does the selenium function elementLocated in node.js throw an error only on Jenkins?

I'm using the function driver.wait(until.elementLocated()) below, written with node.js, as an explicit wait on my Selenium tests to ensure that the pages in my test load properly. When I run the tests from my local CLI they work perfectly, headlessly and with GUI.
const loadMyPage = {
loadThePage: async function(driver) {
try {
await driver.wait(
until.elementLocated(
By.css("div.radio-select span:nth-child(7)")
),
20000
);
} catch (e) {
console.trace(loadMyPage.loadThePage);
throw e;
}
}
However, when I run the tests in Jenkins headlessly I receive the following error every time I use the function elementLocated().
TypeError: Wait condition must be a promise-like object, function, or a Condition object[0m[90m
at Driver.wait (node_modules/selenium-webdriver/lib/webdriver.js:928:13)
at Object.loadMyPage (foobar-tests/page.js:35:20)
at Context.<anonymous> (foobar-tests/foobar.test.js:32:30)
at <anonymous>
Is there anything specific that could cause this error in Jenkins? I have managed to narrow it down to this specific function, elementLocated().
I was able to find a workaround for my issue, however it appears that there is a larger issue at play with selenium. More information on the core issue can be found at https://github.com/SeleniumHQ/selenium/issues/5560.
I updated my explicit wait by passing an additional async function, and this cleared up the problem entirely in Jenkins. Example is below.
loadMyPage: async () => {
//The second async is necessary to run explicit wait functions like the one below.
//This issue is specific to selenium, more information can be found at https://github.com/SeleniumHQ/selenium/issues/5560.
loadThePage: async () {
try {
async driver =>
await driver.wait(
until.elementLocated(
By.css("div.radio-select span:nth-child(7)")
),
10000
);
} catch (e) {
console.trace(loadMyPage.loadThePage);
throw e;
}
}

Resources