-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Description
Jest version: 30.3.0
What happens
Two code paths in jest-circus crash with TypeError: Cannot set properties of undefined (setting 'message') when a test throws a plain object (not an Error instance) and the captured asyncError is undefined.
Location 1: packages/jest-circus/src/formatNodeAssertErrors.ts
} else {
error = asyncError; // asyncError may be undefined
error.message = ...; // CRASH
}When originalError has no .stack (e.g. { status: 403, message: 'Forbidden' }), the else branch runs. If asyncError is undefined, writing error.message throws.
Location 2: packages/jest-circus/src/utils.ts (_getError)
// asyncError = errors[1], which may be undefined when errors is an array
if (error && (typeof error.stack === 'string' || error.message)) {
return error;
}
asyncError.message = `thrown: ${prettyFormat(error, {maxDepth: 3})}`; // CRASHSame crash when errors is a tuple and errors[1] is undefined.
How to reproduce
test('rpc error via done callback', done => {
const { Observable } = require('rxjs');
// subscriber.error() with a plain object (not new Error()) -- common with NestJS RpcException
const obs = new Observable(sub => sub.error({ status: 403, message: 'Forbidden' }));
// no error handler -- unhandled error propagates into jest-circus as [plainObject, undefined]
obs.subscribe(() => done());
});jest-circus records the error as [{ status: 403, message: 'Forbidden' }, undefined]. During test_done:
formatNodeAssertErrorssees no.stackonoriginalError, assignserror = asyncError(undefined), then crashes onerror.message = ..._getErrorhits the same crash on line 436
The test runner aborts with an internal error instead of reporting a clean test failure.
Fix
formatNodeAssertErrors.ts:
} else if (asyncError) {
error = asyncError;
error.message = originalError.message || `thrown: ${prettyFormat(originalError, {maxDepth: 3})}`;
} else {
error = new Error(originalError.message || `thrown: ${prettyFormat(originalError, {maxDepth: 3})}`);
}utils.ts (_getError):
if (asyncError) {
asyncError.message = `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
return asyncError;
}
return new Error(`thrown: ${prettyFormat(error, {maxDepth: 3})}`);Both fixes fall back to constructing a new Error when asyncError is undefined.
Notes
Circus.TestError types the tuple second element as Exception (non-optional), but at runtime it can be undefined, so there is also a type/runtime mismatch to fix there.