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
Testclass containing anUUIDcreated using the uuid package in the constructor; - B: consumes that class. Currently, it only contains a test and, for testing purpose, we mock the
uuidpackage soTestalways 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-sdkon version3.3.2;node-notifieron 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.