Dependency Hell with Yarn & Jest
21 Jun 2020 - JavaScript, TypeScript, Yarn, Jest
Let’s discuss dependency hell when mocking Node module with Jest while using Yarn as the package manager.
Context
To illustrate the issue let’s say we have a monorepo (handled using Yarn Workspaces) with three packages:
- A: provides a
Test
class containing anUUID
created using the uuid package in the constructor; - B: consumes that class. Currently, it only contains a test and, for testing purpose, we mock the
uuid
package soTest
always contains the sameUUID
; - C: independent of the above projects, it has some external dependencies.
Here are the associated files:
./package.json
|
|
./packages/my-project-a/index.js
|
|
./packages/my-project-a/package.json
|
|
./packages/my-project-b/package.json
|
|
./packages/my-project-b/test/index.test.ts
|
|
./packages/my-project-c/package.json
|
|
I used the following versions:
- Node: 12.16.3;
- Yarn: 1.22.4.
Unfortunately if we run the test from project B
then the mock does not seem to work very well:
|
|
It seems that locally (line 7) we used the mocked version while Test
(line 8) relied on the actual uuid
package.
Having done that a couple of times in the past, it was an unexpected result 😔.
Dependency Hell
After investigating for few hours, I found the following for the uuid
package:
|
|
The package uuid
seems to be installed several times, with different versions. This is due to Yarn:
- our projects depend on version
8.1.0
; aws-sdk
on version3.3.2
;node-notifier
on version7.0.3
;- other dependencies on version
3.4.0
.
One of the version (here 3.4.0
) has been hoisted (from Yarn maintainers,
the algorithm choosing the hoisted version should be considered as a blackbox, and might change in the future).
From my understanding, when the test from project B
mocks uuid
it currently mocks the version 3.4.0
, due to Node
module resolution strategy, while Test
from project A
will use version 8.1.0
(due to the resolution strategy, again).
Workaround
I am a bit annoyed as there is no official way to ensure my version of uuid
is hoisted. So far, I was able to “force” Yarn by
using this crappy hack 💩:
|
|
as I know how the algorithm works from a Yarn contributor:
Normally yarn will take whichever version has the most references to it and make it the hoisted top-level version, because that would result in the least duplication (least amount of wasted drive space)
After running yarn
, I now have:
|
|
Fortunately I have a test to validate this behavior. I am more worried that the workaround will not be needed anymore at some point, which would result in dead code (and complicated code for other engineers as I can’t easily comment it).
Note: nohoist is not a solution. On the contrary, it would ensure
that a mocked version of the uuid
package will never be used in a different package.