You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Concurrent ESM mock imports duplicate instances and stale factories overwrite reset cache #16539
The reproduction uses native ESM, transform: {} and --experimental-vm-modules. It contains two tests:
Register an asynchronous jest.unstable_mockModule factory and concurrently import its target with Promise.all.
Start a factory, call jest.resetModules(), import the new mock, then let the old factory finish.
Expected behavior
Concurrent imports should invoke the factory once and return the same module namespace and exported object.
An old factory should complete for its original caller without replacing the newer mock cached after resetModules().
Actual behavior
Both tests fail on 30.5.2:
concurrent imports share one mock factory result
Expected number of calls: 1
Received number of calls: 2
an old factory does not overwrite a newer mock after resetModules
Expected: 2
Received: 1
The concurrent imports produce distinct namespaces and exported object identities. When the older factory finishes last, subsequent imports receive the old generation.
Additional context
In main's EsmLoader.importMock, the cache is checked before await factory(), but the result is registered only after the factory completes. Concurrent callers therefore all see a cache miss. The eventual write also uses the registry active at completion, which can belong to a newer reset or isolation scope.
The same lifecycle needs completion handling in importMockSync: it caches an asynchronous factory's Promise, but never promotes the fulfilled entry to its evaluated module. On Node 24, a completed asynchronous import can therefore still make a subsequent require() report ERR_REQUIRE_ASYNC_MODULE.
I have a fix that caches the pending operation, retains its original registry, and updates or removes the entry only while it still belongs to that operation. Regression tests cover concurrent sync/async factories, shared rejection and retry, reset completion ordering, isolation and synchronous reuse after fulfillment.
Environment
System:
OS: macOS 27.2
CPU: (12) arm64 Apple M4 Pro
Binaries:
Node: 22.23.4 (also tested main with Node 24.22.1)
npm: 10.9.9
npmPackages:
jest: 30.5.2 => 30.5.2
Activity