I am unable to reach window.onerror
in React tests with vitest, jsdom, and React Testing Library when throwing inside an event handler. The same test passes in Jest.
Goal: Assert that window.onerror
is called when an error is thrown in an event handler (e.g., onClick
). It seems that vitest with jsdom doesn’t propagate JS errors to window.onerror
?
Vitest code
import { render, screen } from "@testing-library/react";
import ReactTestUtils from "react-dom/test-utils";
import { vi, it, expect } from "vitest";
import React from "react";
it("window.onerror is called on unhandled error in event handler", async () => {
const spy = vi.spyOn(console, "error");
spy.mockImplementation(() => undefined);
const caught = vi.fn();
const App = () => {
return (
<button
onClick={() => {
throw new Error("ahhhh");
}}
>
Error
</button>
);
};
class Tracker extends React.Component<any, any> {
componentDidMount() {
window.onerror = (message, source, lineno, colno, error) => {
caught();
return true;
};
}
render() {
return this.props.children;
}
}
render(
<Tracker>
<App />
</Tracker>
);
expect(caught).toHaveBeenCalledTimes(0);
const button = await screen.findByText("Error");
try {
// using ReactTestUtils here since it allows me to catch the error
ReactTestUtils.Simulate.click(button);
} catch (e) {
// do nothing
}
expect(caught).toHaveBeenCalledTimes(1);
});
Dependencies
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@testing-library/react": "^14.1.2",
"@vitejs/plugin-react": "^4.2.0",
"jsdom": "^23.0.1",
"vite": "^5.0.4",
"vitest": "^0.34.6"
The test fails with the following error message:
FAIL index.test.tsx > window.onerror is called on unhandled error in event handler
AssertionError: expected "spy" to be called 1 times, but got 0 times
❯ index.test.tsx:52:18
50| }
51|
52| expect(caught).toHaveBeenCalledTimes(1);
| ^
53| });
54|
The same test passes with Jest (Jest code below). Note that the code is very much the same. I only replaced jest
with vi
.
Jest code
import { render, screen } from "@testing-library/react";
import ReactTestUtils from "react-dom/test-utils";
import { jest, it, expect } from "@jest/globals";
import React from "react";
it("window.onerror is called on unhandled error in event handler", async () => {
const spy = jest.spyOn(console, "error");
spy.mockImplementation(() => undefined);
const caught = jest.fn();
const App = () => {
return (
<button
onClick={() => {
throw new Error("ahhhh");
}}
>
Error
</button>
);
};
class Tracker extends React.Component {
componentDidMount() {
window.onerror = (message, source, lineno, colno, error) => {
caught();
return true;
};
}
render() {
return this.props.children;
}
}
render(
<Tracker>
<App />
</Tracker>
);
expect(caught).toHaveBeenCalledTimes(0);
const button = await screen.findByText("Error");
try {
// using ReactTestUtils here since it allows me to catch the error
ReactTestUtils.Simulate.click(button);
} catch (e) {
// do nothing
}
expect(caught).toHaveBeenCalledTimes(1);
});
Dependencies
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3",
"@testing-library/react": "^14.1.2",
"babel-jest": "^29.7.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react-test-renderer": "^18.2.0"
Looking for ways to work around this. Any way I can reach window.onerror
in vitest?
2
Answers
I was able to fix the vitest test by replacing
window.onerror =
withwindow.addEventListener('error', ...)
.In your vitest setup, you can manually mock window.onerror before the test and then assert whether it was called:
Create an error boundary component and use it to capture errors:
If you have control over the error-throwing logic, you can dispatch a custom event and listen for that in your tests: