Open blimmer opened 3 years ago
Happens with axios as well (when axios throws error)
node:internal/child_process/serialization:127
const string = JSONStringify(message) + '\n';
^
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'socket' -> object with constructor 'Object'
--- property '_httpMessage' closes the circle
at stringify (<anonymous>)
at writeChannelMessage (node:internal/child_process/serialization:127:20)
at process.target._send (node:internal/child_process:839:17)
at process.target.send (node:internal/child_process:739:19)
at reportSuccess (..../node_modules/jest-worker/build/workers/processChild.js:59:11)
Workaround: 1) add to jest.config
setupFilesAfterEnv: [ './jest.axios-workaround.setup.ts'],
2) file jest.axios-workaround.setup.ts
import axios from 'axios'
axios.interceptors.response.use(
(response) => response,
(error) => {
if ('request' in error) delete (error as any).request
if ('response' in error) delete (error as any).response?.request
return Promise.reject(error)
},
)
(or if you use js, simply remove types)
If you still need internals of request you can use this approach
function axiosErrorWorkaround(error: AxiosError) {
if (!error?.request) return
const picked = _.pick(error.request, [
'outputData',
'writable',
'destroyed',
'chunkedEncoding',
'shouldKeepAlive',
'maxRequestsOnConnectionReached',
'useChunkedEncodingByDefault',
'sendDate',
'finished',
'method',
'maxHeaderSize',
'path',
'aborted',
'timeoutCb',
'upgradeOrConnect',
'host',
'config',
'_header',
'_keepAliveTimeout',
])
error.request = picked
if (error.response?.request) {
error.response.request = picked
}
}
For others running in to this, @brudil found that Jest is not respecting the prototype when doing the cross-worker serialization, so if you've implemented toJSON
as a class method, it will get ignored.
If you implement toJSON
as a method directly on the instance, then it'll work fine.
@npwork your first solution did not work for me (it caused my Axios aggregator to throw an error):
Auth › should throw 400 error when lastName is not a valid string; undefined
AggregateError:
at Function.Object.<anonymous>.AxiosError.from (../../node_modules/axios/lib/core/AxiosError.js:89:14)
at RedirectableRequest.handleRequestError (../../node_modules/axios/lib/adapters/http.js:610:25)
at ClientRequest.eventHandlers.<computed> (../../node_modules/follow-redirects/index.js:38:24)
at Axios.request (../../node_modules/axios/lib/core/Axios.js:45:41)
Cause:
AggregateError:
And your second solution fixes that issue but produces a new one :smiling_face_with_tear::
TypeError: Cannot read properties of undefined (reading 'status')
25 | );
26 |
> 27 | expect(res.status).toBe(200);
| ^
28 | expect(res.data).toStrictEqual({
29 | some: 123,
30 | });
at src/auth/auth-validation.spec.ts:27:18
at fulfilled (../../node_modules/tslib/tslib.js:166:62)
Here is how I did it:
import axios from 'axios';
import _ from 'lodash';
axios.interceptors.response.use(
(response) => response,
(error) => {
if (!error?.request) {
return;
}
const picked = _.pick(error.request, [
'outputData',
'writable',
'destroyed',
'chunkedEncoding',
'shouldKeepAlive',
'maxRequestsOnConnectionReached',
'useChunkedEncodingByDefault',
'sendDate',
'finished',
'method',
'maxHeaderSize',
'path',
'aborted',
'timeoutCb',
'upgradeOrConnect',
'host',
'config',
'_header',
'_keepAliveTimeout',
]);
error.request = picked;
if (error.response?.request) {
error.response.request = picked;
}
},
);
At least I realized that it had nothing to do with axios and jest :sweat_smile:, so I added the setupFileAfterEnv
so to understand what is going on and it helped me:
import axios from 'axios';
axios.interceptors.response.use(
(response) => response,
(error) => {
console.dir(error, { depth: null });
return Promise.reject(error);
},
);
Version
28.1.1
Steps to reproduce
npm ci
npm run test:jest-jasmine2
. You'll see that the error message from the test is properly serialized. You can see that output in GitHub actions without cloning the repo.npm run test:jest-circus
. You'll see an error message aboutConverting circular structure to JSON
. You can see that output in GitHub actions without cloning the repo.In my test, I have three classes:
Thing
,ThingManager
(which tracks allThing
s) and a customThingError
that has a reference to athing
: https://github.com/blimmer/jest-issue-repro/blob/0ccc0debd32599c2415f9e9dd1c727aa4b1c075c/circular-structure.test.js#L1-L40The circular reference issue is because each
Thing
has a reference to itselfvia ThingManager.things
. In the standard Node world, I work around this by defining atoJSON
method onThing
that removes the circular reference: https://github.com/blimmer/jest-issue-repro/blob/0ccc0debd32599c2415f9e9dd1c727aa4b1c075c/circular-structure.test.js#L24-L26Expected behavior
I expected
jest-circus
to serialize the error message (by callingtoJSON
) just likejest-jasmine2
did.Actual behavior
When running with
jest-circus
, I get an error message about the JSON serialization failing, not the error message from the test itself. This makes it difficult to debug which test is failing, as there's no reference to the issue in the output.I think I tracked the issue down to this code in
jest-circus
: https://github.com/facebook/jest/blob/7dd17d541bcdb4d17d96b53586949fb195294040/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L96-L99Because
keepPrototype
is set tofalse
, I believe it's discarding thetoJSON
method, which triggers the issue. The code comment makes it seem like this might be a known issue that you're already looking to correct.Additional context
Interestingly enough, this repro case also exposes the issue here described in https://github.com/facebook/jest/issues/10577 .
If you look at this Github Actions run, you'll notice that the build hangs (and is killed by my 1-minute timeout on the action) on Node 12.x and 14.x.
Environment