Jest "Cannot find module" - Fix Resolution Errors in CI
Jest could not resolve an import. Either the package was never installed, the file path is wrong (often a case mismatch that only matters on Linux), or a path alias is not mapped in the Jest config.
What this error means
A test file fails at collection with Cannot find module 'X' from 'Y'. It frequently passes locally on macOS but fails on a Linux CI runner because the filesystem there is case-sensitive.
Cannot find module '@/utils/format' from 'src/components/Card.test.tsx'
Require stack:
src/components/Card.test.tsxDiagnose it: flake, environment, or genuine failure?
Before debugging the assertion, establish whether the test is deterministic. A test that fails only in CI is usually order-dependent, time-dependent, or racing something, and fixing the assertion will not help.
# does it fail in isolation?
npx vitest run path/to/file.test.ts
# is it order dependent? run the suite in a random order twice
npx vitest run --sequence.shuffle
# is it a race? run the same file repeatedly
for i in $(seq 1 20); do npx vitest run path/to/file.test.ts || break; doneCommon causes
Path alias not mapped in Jest
A @/... alias works in your bundler and tsconfig paths, but Jest resolves modules itself and needs the same mapping in moduleNameMapper. Without it, the import is unresolvable.
Case-sensitive import on Linux
Importing ./Card when the file is card.tsx works on case-insensitive macOS/Windows but fails on a case-sensitive Linux runner.
Dependency not installed
The package is in devDependencies but CI ran npm ci --omit=dev, or it was never added to package.json, so it is absent from node_modules.
How to fix it
Map path aliases in Jest config
Mirror your tsconfig paths into moduleNameMapper so Jest resolves aliases the same way your build does.
// jest.config.js
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};Fix the import case to match the file
- Compare the import path against the real filename, including capitalization.
- Rename the import (or the file) so they match exactly.
- Add
eslint-plugin-importwithimport/no-unresolvedto catch this before CI.
Install the missing dependency
npm install <package>
# if it is needed for tests, ensure CI installs devDependencies (no --omit=dev)CI-only causes worth ruling out
- Runners have fewer cores than a laptop, so timing-sensitive tests that pass locally fail under contention.
- No TTY and a different locale or timezone. Snapshot tests containing formatted dates or numbers are the usual casualty; pin
TZandLANGin the job. - Parallel workers sharing a database, a port, or a temp directory. Give each worker its own namespace.
- Default timeouts calibrated on a fast machine. A cold runner is slower on first execution, especially before any cache warms.
How to prevent it
- Keep
moduleNameMapperin sync with tsconfigpaths. - Develop or lint on a case-sensitive filesystem to catch case bugs early.
- Commit the lockfile and run
npm ciso installs are reproducible.