Dependency Hell with Yarn & Jest
Let’s discuss dependency hell when mocking Node module with Jest while using Yarn as the package manager.
To illustrate the issue let’s say we have a monorepo (handled using Yarn Workspaces) with three packages:
- A: provides a
Testclass containing an
UUIDcreated using the uuid package in the constructor;
- B: consumes that class. Currently, it only contains a test and, for testing purpose, we mock the
Testalways contains the same
- C: independent of the above projects, it has some external dependencies.
Here are the associated files:
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
Having done that a couple of times in the past, it was an unexpected result 😔.
After investigating for few hours, I found the following for the
uuid seems to be installed several times, with different versions. This is due to Yarn:
- our projects depend on version
- other dependencies on version
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
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).
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)
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.