jest: Circular references hang jest when assertions fail on node 14
đ Bug Report
When an assertion fails where either the expected or actual value is circular, and both values are objects, jest encounters an error stating it failed to convert a circular structure to JSON, resulting in the test run not completing.
To Reproduce
it("test", () => {
const foo = {};
foo.ref = foo;
expect(foo).toEqual({});
});
Running jest gives me the following error:
(node:11685) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
--- property 'ref' closes the circle
at stringify (<anonymous>)
at writeChannelMessage (internal/child_process/serialization.js:117:20)
at process.target._send (internal/child_process.js:804:17)
at process.target.send (internal/child_process.js:702:19)
at reportSuccess (/Users/verit/basic-jsx/node_modules/jest-worker/build/workers/processChild.js:67:11)
Jest continues running indefinitely (I only tested up to ten minutes) and reports nothing regarding the test suite.
I traced this to the added failureDetails
property on error messages, landed in 26.3.0.
Expected behavior
Iâd expect the test to fail and jest to complete running.
envinfo
I only tested two versions. The above error occurs on 14.9.0, but does not on 12.16.1.
System:
OS: macOS 10.15.6
CPU: (8) x64 Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz
Binaries:
Node: 14.9.0 - ~/.nodenv/versions/14.9.0/bin/node
npm: 6.14.8 - ~/.nodenv/versions/14.9.0/bin/npm
npmPackages:
jest: ^26.4.2 => 26.4.2
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 81
- Comments: 49 (10 by maintainers)
Commits related to this issue
- fix(jest-worker): Remove circular references from messages sent to workers Fixes #10577 — committed to ziacik/jest by ziacik 4 years ago
- fix(jest-worker): Remove circular references from messages sent to workers Fixes #10577 — committed to ziacik/jest by ziacik 4 years ago
- fix(jest-worker): Remove circular references from messages sent to workers Fixes #10577 — committed to ziacik/jest by ziacik 4 years ago
- fix(jest-worker): Remove circular references from messages sent to workers Fixes #10577 — committed to ziacik/jest by ziacik 4 years ago
- downgrade jest https://github.com/facebook/jest/issues/10577 — committed to genome-spy/genome-spy by tuner 4 years ago
- debug command, also specify exactly how to run node per https://github.com/facebook/jest/issues/10577 as we use circular references — committed to rwilcox/markdown-mindmap by rwilcox 3 years ago
- fix: failed expect with circular references hangs jest worker relates #10577 — committed to nodkz/jest by deleted user 8 months ago
- fix: failed expect with circular references hangs jest worker relates #10577 — committed to nodkz/jest by nodkz 8 months ago
For Angular users this might be the fix/workaround:
npm run test:detectOpenHandles
or the equivalentjest --detectOpenHandles
OR
BrowserAnimationsModule
orNoopAnimationsModule
in your testI donât know what the above errors are caused by though. Hopefully this helps.
EDIT: Formatting
@Lonli-Lokli Looks like the only workaround for now is
--detectOpenHandles
However, this causes a massive decrease in performance.
I hope this can be fixed soon. I have migrated several of our projects to jest⌠and now it causes hiccups all throughout our build system when one test breaks.
It would be nice if
--testTimeout
worked in this scenario⌠but it still allows the test to just hang until Jenkins or circle ci times out.Same issue on node 18
Node v16.19 Jest 29.5 simply calling a method that raises an error with a circular reference.
Try/catching the method call and replacing the error in the catch is a work-around. Without that, jest throws the unintuitive error message. Example:
The underlying dependency of the package throwing the error in this case is Axios.
The root problem with Jest is assuming JSON.stringify is always going to work. Use a safe stringify implementation, please.
In our case this ended up being an uncaught exception. We eventually were able to log out the exception, and fix the unit test. Then it worked fine. Just FYI if anyone else runs into this. Scary stuff.
@gaurav5430 because with
--runInBand
everything (assertion failures/etc.) stays within a single Node process.Itâs only when Jest has N child worker processes that, when a test fails in the child worker, the assertion failure needs to be serialized (via JSON) and sent to the parent process, and then a âcannot convert to JSONâ error happens.
@Lonli-Lokli I have the exact same versions as you, still getting meaningless errors:
The error should have been a NullInjectorError:
NullInjectorError: No provider for CloudApiClient
Why not just use https://github.com/WebReflection/circular-json or https://www.npmjs.com/package/json-stringify-safe or similar libraries to be safe with Object JSON stringify?
I have debugged a bit why this happens, and it happens because Angular TestBed configure its compiler to add some additional meta-info to the errors. That configuration is getted from the same file, calling to
initServicesIfNeeded
function from some place into TestBed.I donât know if it should be fixed by jest or Angular (maybe implementing a
toJSON
method to its errors, to avoid to include this meta-properties to serialization). But itâs a problem with jest-workers and Angular TestBed errorsWe confirm it happens in Node 12, and itâs more common when using Angular Dependency Injection (I think they have cyclic structures in some error-states).
The process hangs in such scenario, but this can be improved slightly be applying
--unhandled-rejection=strict
to nodejs script, instead running jest as separate binary. It helpsjest
to recover and fail suite (but it does not resolve cyclic reference of course).Similar problem -
expect(someDomeNode).toBe(someOtherNode)
loops somewhere in the serialization of endless React/JSDOM structure and ends as an OOM exception.I get this issue with mocking await functions in many tests in different
describes
in the same file which all fail when I have amockReturnValueOnce
in one describe, but doesnât appear withmockReturnValue
in thedescribe
block, itâs as if the mocks are shared betweendescribe
blocks because the return value from an upperdescribe
was being returned in my otherdescribe
block mock instead of its own return valueâŚIf I do
jest --detectOpenHandles
orjest --runInBand
the errors donât appear, and I donât have a bunch of tests fail anymore.I was creating a repro case for a similar issue (see #11958) and I noticed that the issue of the
jest
runner hanging only appears to affectjest-circus
on Node 12.x and 14.x.If you look at this GitHub actions run: https://github.com/blimmer/jest-issue-repro/actions/runs/1339553822 youâll see that 12.x and 14.x get killed by a timeout, while 16.x completes normally.
So, for those who are able to upgrade to Node 16.x, thatâs another possible workaround to this issue.
Glad to see this is not a general problem with testing eslint rules. In my case, when a test fails, because almost all eslint nodes have circular references this problem is quite common.
I think we can fix this by patching the jest-worker, when detecting a circular error, just fail the case.
Currently a solution could be
workerThreads: true
option.Note that it is marked experimental. That is recently added feature and it is not tested widely yet.
The fix for this will be reverted (#11172) as it caused other serialization issues. Might need to use a custom json serializer, yeah
Added a PR as an attempt to fix this.
Also, please note that the
messageParent
error mentioned above is due to this line which swallows the real error message which is also about circular references.Trying to stringify the
req
object from Axios will result in the circular reference: req -> res -> req As your error states:Try comparing the properties of the request or response object that you care about rather than the whole object itself.
I.e.
expect(res.body?.myProperty).toEqual("foo")
whatâs the actual solution for this issue? would downgrading jest work ?
@SimenB Iâve dug into this and confirmed that if I comment out this line the issue goes away, and also that this is because
test.errors
holds both Jest errors and user errors.So to ensure that that is JSON serializable I think weâve got three options:
AssertionResult
), which internally would be ensured are JSON serializableAssertionResult
is only represented as a type so weâd have to come up with a stable way to check if an error is of that type (which could be as simple asmatcherResult in error
, but itâd have to be stable)flatted
to serialize the contentI think 1. is probably the option to go with at least for - afaik JetBrains are actually the only people using this property and theyâre definitely only interested in the
AssertionResult
errors from Jest; I think that should also be easier to extend afterwards if someone did want to preserve user errors e.g. we could implement a config option for providing a handler to do the serialization and only apply that to nonAssertionResult
errors.Personally, I recommend rolling back the jest-jasmine2 (or jest-circus) version to one that isnât broken. I havenât tried it for a while yet, so there might be mysterious failures if you use try to use features introduced by the newer jest versions; you can do that if you donât directly depend on jest-jasmine2 by adding this to your package.json:
Note that after commit 5f6f2ec8e17555b695d65ab68824926c77730216 which changes default runner to circus, the error message is
with
JEST_JASMINE=1
the error is as before.