Runners built with `create-jest-runner` do not work with next.js (or presumably any other projects that have configurations that cannot be cloned)
cprussin opened this issue · 13 comments
So, this is a strange issue, and I'm not entirely sure what's going on yet, but I do have a minimal repro case that illustrates the issue.
Basically, given the following jest config:
const nextJest = require("next/jest");
const createJestConfig = nextJest({ dir: "./" });
module.exports = async () => ({
projects: [
await createJestConfig({
displayName: "unit-test"
})(),
{
displayName: "lint",
runner: "jest-runner-eslint",
testMatch: ["<rootDir>/pages/**/*.tsx"]
}
]
});
Then running jest
results in:
❯ npm run test
> repro@1.0.0 test
> jest
FAIL lint pages/index.tsx
● Test suite failed to run
()=>null could not be cloned.
at ChildProcessWorker.send (node_modules/jest-runner-eslint/node_modules/jest-worker/build/workers/ChildProcessWorker.js:279:17)
FAIL lint pages/page2.tsx
● Test suite failed to run
()=>null could not be cloned.
at ChildProcessWorker.send (node_modules/jest-runner-eslint/node_modules/jest-worker/build/workers/ChildProcessWorker.js:279:17)
Test Suites: 2 failed, 2 total
Tests: 0 total
Snapshots: 0 total
Time: 0.122 s
Ran all test suites.
I think that what's going on here is that create-jest-runner
is trying to send the entire jest config (rather than just the project's config), to the jest-worker
threads, which involves deep cloning the config object. However, it seems that next/jest
includes closures that cannot be cloned in it's project config.
Presumably, I'm guessing this is also going to be broken if there's any other project in the jest config that includes any fields that cannot be cloned.
I'm not really sure as I don't know anything at all about how create-jest-runner
or jest-worker
work so I could be way off here. Would appreciate any tips / thoughts to help me investigate and potentially fix this one.
Hmm, interesting. Without looking at you reproduction specifically (currently on my way to the airport), all config should be made serialiazable for exactly that reason during Jest's normalize
. So this seems more like a bug in Jest core rather than this module.
Your reproduction does not include a unit test. Somehow randomly I added one and everything works.
Usage example from Next documentation works as expected:
const nextJest = require("next/jest");
const createJestConfig = nextJest({ dir: "./" });
module.exports = createJestConfig({
displayName: "unit-test",
});
Also it is enough to replace jest-runner-eslint
with jest-runner-tsd
and the problem is gone. The tsd runner is also using create-jest-runner
. Hm.. Interesting what is going on.
I'd guess one should spread next config, then merge any projects
or some such. But I'd need to debug
That’s right! Works for me simply like this:
const nextJest = require("next/jest");
const createJestConfig = nextJest({ dir: "./" });
module.exports = {
projects: [
{ ...createJestConfig({ displayName: "unit-test" }) },
{
displayName: "lint",
runner: "jest-runner-eslint",
testMatch: ["<rootDir>/pages/**/*.tsx"],
},
],
};
Your reproduction does not include a unit test. Somehow randomly I added one and everything works.
Usage example from Next documentation works as expected:
const nextJest = require("next/jest"); const createJestConfig = nextJest({ dir: "./" }); module.exports = createJestConfig({ displayName: "unit-test", });Also it is enough to replace
jest-runner-eslint
withjest-runner-tsd
and the problem is gone. The tsd runner is also usingcreate-jest-runner
. Hm.. Interesting what is going on.
@mrazauskas Hmm that's super interesting! I'm finding this to be inconsistent -- sometimes it works, other times it doesn't, but it only works if there's at least one unit test. Smells suspiciously like a race condition to me.
I'm also seeing identical behavior with jest-runner-tsd
-- seems to fail consistently if there's no unit tests, seems to fail inconsistently if there's at least one unit test.
Also, when I add both a project using jest-runner-tsd
and one using jest-runner-eslint
, it seems to fail much more consistently, even in the presence of a unit test. Additionally, the order of the projects seems to have an impact -- if I put the next/jest
project last, jest seems to execute it first, which seems to add enough of a delay to whatever is racy that the other two projects pass much more consistently (but not entirely consistently).
I'd guess one should spread next config, then merge any
projects
or some such. But I'd need to debug
@SimenB Hmm ok so I tried this and it seems to work consistently:
const nextJest = require("next/jest");
const createJestConfig = nextJest({ dir: "./" });
module.exports = createJestConfig({
projects: [
{ displayName: "unit-test" },
{
displayName: "lint",
runner: "jest-runner-eslint",
testMatch: ["<rootDir>/pages/**/*.tsx"]
}
]
});
So that's great! That might mean the issue is closed. However, I'm not convinced that the issue is actually resolved, because even with my original config, both projects work in isolation, and combining them results in racy behavior. Do you think it's worth keeping the ticket open and doing any investigation or should I just close it as a spooky mystery of a misused next/jest
?
That’s right! Works for me simply like this:
const nextJest = require("next/jest"); const createJestConfig = nextJest({ dir: "./" }); module.exports = { projects: [ { ...createJestConfig({ displayName: "unit-test" }) }, { displayName: "lint", runner: "jest-runner-eslint", testMatch: ["<rootDir>/pages/**/*.tsx"], }, ], };
@mrazauskas I don't think that's right -- createJestConfig
returns an async function, and spreading it is nonsensical. From my testing, this config works for eslint, but it breaks unit testing as the first project is malformed (so I don't think it really fixes anything, it's effectively just disabling the next config). However, see above for what does seem to work consistently.
Shouldn’t be any race condition. Jest is not always using workers and the error is triggered only if config reaches a worker. That makes it look as if there is a race condition. For examples, I was adding jest-runner-tsd
runner, but didn’t add any type tests to run. So it looks like the problem is gone, but simply not a single worker was being created.
You are right, my solution does not work.
I take back my earlier statement about wrapping the whole thing in createJestConfig
-- that actually won't work since the settings generated by next/jest
need to live in the unit-test
project. I suppose you could do some spreading & copying to get the settings copied into the project... but I'm really questioning at that point if it's actually solving anything since presumably copying the same config back into the project would result in the same problem as if it were just generated into the project in the first place.
So, tl;dr, I'm back to having no clue what the cause or the right fix is, but I'm fairly sure my original jest config is correct.
I'm also reproducing this much more consistently (but not entirely consistently) on a real project with unit tests than I was on the minimal repro case
Update: I had some more time to look into this, it appears the issue is caused by the next config (which contains a function generateBuildId
) getting attached to the transformer configuration here.
I tested to confirm by adding the line delete request[3][0].globalConfig.projects[0].transform['^.+\\.(js|jsx|ts|tsx|mjs)$'][1].nextConfig.generateBuildId;
right before the request
is sent to the child process in jest-worker
here. Deleting that function from the config indeed does fix the problem.
It doesn't look like anything in jest's normalize function would do anything to normalize the config passed to the transformer, specifically this line explicitly passes through such config untouched. I'm not sure how this should work so I don't know the right fix -- @SimenB you mentioned this looks like a bug in Jest to you, can you advise on how you believe the normalizer should handle this kind of situation? I'd be happy to put in a PR. Otherwise, would it be more appropriate to go to the next.js repo and have them do some kind of normalization on their side instead?
(also this is pretty clearly not a bug with create-jest-worker
at this point so I'd be happy to close this ticket and move over to either next or jest, depending on which project ya'll advise needs to change)
Interesting findings.
For me it looks like this is Next bug, because the resulting Jest config object must be JSON-serializable. That is clearly documented. Transformer options is part of the config object.
@mrazauskas OK, that makes sense -- I'll go put in a bug report and a fix over there. Thanks!
bug report submitted to next (in case anyone cares to follow): vercel/next.js#47407