Next.js with Jest Install guide - and fixing a string-width import issue

Posted 2024-09-29

This is part of my notes section of the site. It is where I post quick notes/cheatsheets/etc.
For more in-depth articles which are (hopefully) more interesting check out my blog


This is a short blog post about a specific issue I faced on Next.js. This isn't the normal style of blog post that I write, and I'm documenting this only so it may help anyone Googling for this error.

If you run npx create-next-app@latest --example with-jest with-jest-app, and then add a test file called index.test.tsx with the code snippet below, it will run the test without issues when you run yarn test:

index.test.tsx
✅ copied
describe("Dummy test to run something...", () => { test('some basic test', ()=> { expect(1).toBe(1) expect(false).toBe(false) }) })

But if you already have a NextJS app (or just installed it via the instructions), and then follow the 'manual setup' instructions on the Next.js documentation nothing will run when you call yarn test.

This page is my guide on how to set up Jest to run correctly in yarn, with an existing NextJS app, and to get around the hurdles along the way.

Initial app set up

This section is just installing Next.js - you can skip this if you already have a Next app.

Let's start with a simple installation:

npx create-next-app@latest

I selected the default options:

What is your project named? … next-existing-app-jest-demo Would you like to use TypeScript? Yes Would you like to use ESLint? Yes Would you like to use Tailwind CSS? Yes Would you like to use `src/` directory? Yes Would you like to use App Router? (recommended) Yes Would you like to customize the default import alias (@/*)? No

Adding Jest to the existing NextJS app, by following the official docs

Following the advice on manual setup part of the official docs the following commands need to be run:

yarn add -D jest jest-environment-jsdom\ @testing-library/react @testing-library/dom\ @testing-library/jest-dom

Then set up the Jest config:

yarn create jest@latest

I answered with the following:

✅ copied
Would you like to use Jest when running "test" script in "package.json"? yes Would you like to use Typescript for the configuration file? yes Choose the test environment that will be used for testing › jsdom Do you want Jest to add coverage reports? yes Which provider should be used to instrument code for coverage? › v8 Automatically clear mock calls [...] before every test? no

This will have created a jest.config.ts file (see the version that was created for me here)

Then the docs say to replace this with their custom Next.js Jest config, so now replace the jest.config.ts file with the following:

jest.config.ts
✅ copied
import type { Config } from 'jest' import nextJest from 'next/jest.js' const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }) // Add any custom config to be passed to Jest const config: Config = { coverageProvider: 'v8', testEnvironment: 'jsdom', // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async export default createJestConfig(config)

And finally let's add a sample test file. Create src/app/page.test.tsx, and put in this simple test:

page.test.tsx
✅ copied
describe("Some dummy test", () => { test('some small test to run', ()=> { expect(1).toBe(1) expect(true).toBe(true) }) })

But if you run yarn test (as of September 2024), you will see the following error:

✅ copied
➜ $ yarn test Error: Jest: Failed to parse the TypeScript config file ./jest.config.ts Error: Jest: 'ts-node' is required for the TypeScript configuration files. Make sure it is installed Error: Cannot find package 'ts-node' imported from ./node_modules/@jest/core/node_modules/jest-config/build/readConfigFileAndSetRootDir.js at readConfigFileAndSetRootDir (./node_modules/@jest/core/node_modules/jest-config/build/readConfigFileAndSetRootDir.js:116:13) at async readInitialOptions (./node_modules/@jest/core/node_modules/jest-config/build/index.js:403:13) at async readConfig (./node_modules/@jest/core/node_modules/jest-config/build/index.js:147:48) at async readConfigs (./node_modules/@jest/core/node_modules/jest-config/build/index.js:424:26) at async runCLI (./node_modules/@jest/core/build/cli/index.js:151:59) at async Object.run (./node_modules/jest-cli/build/run.js:130:37) error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Ok, so now let's figure out how to get this working!

Fix approach 1: don't use TypeScript in the jest config

The easiest approach is to simply rename it to jest.config.js, and replace it's contents with this:

jest.config.js
✅ copied
const nextJest = require('next/jest') /** @type {import('jest').Config} */ const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }) // Add any custom config to be passed to Jest const config = { coverageProvider: 'v8', testEnvironment: 'jsdom', // Add more setup options before each test is run // setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async module.exports = createJestConfig(config)

Then yarn test will run.

You can still run tests against your Typescript files - it only affects this jest.config.ts file.

Practically speaking this is the easiest approach, and has very little impact of being a JS file and not TypeScript. You are unlikely to make many changes to this jest config file. But I woudl still prefer to use TypeScript, so I carried on looking into this.

Approach 2: Get it working with Typescript (and seeing the import issue we need to fix)

Despite the above approach working (using a Javascript config file and not Typescreipt file) I wanted to understand why the .ts file wouldn't work.

Let's add ts-node, like the current error tells us to do:

yarn add -D ts-node

Now we get a different error - about a module import issue from the string-width dependency:

✅ copied
yarn test $ jest Error [ERR_REQUIRE_ESM]: require() of ES Module ./node_modules/string-width/index.js from ./node_modules/cliui/build/index.cjs not supported. Instead change the require of index.js in ./node_modules/cliui/build/index.cjs to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (./node_modules/cliui/build/index.cjs:291:21) error Command failed with exit code 1.

Looking online this seems to a common bug (similar one reported here) - this gives the first hint that this issue is possibly just when using yarn.

Git commit your changes, then delete yarn.lock and reinstall (with yarn). Then yarn test starts working.

Looking at the diff in yarn.lock, it seems to change the string-width package.

yarn.lock.diff
✅ copied
diff --git a/yarn.lock b/yarn.lock index e1f1a62..b2e5bf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4394,7 +4394,6 @@ string-length@^4.0.1: strip-ansi "^6.0.0" "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4940,7 +4939,6 @@ word-wrap@^1.2.5: integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

Now we have at least half figured out one part of the issue. Let's git stash, and see what other approaches we can do to fix this.

BTW You can find a good resource on GitHub about this string-width issue, and this good guide to v5 of the package, which only supports esm.

Approach 3: Using yarn resolutions to force a specific version of string-width (did not work for me)

According to a few posts on GitHub, we could use a resolutions in your package.json:

"resolutions": { "string-width": "4.2.3" }

But this had no effect for me.

I was surprised as 4.2.3 should have been working, but it would break installs completely for me.

Other posts on Github indicate this has worked for others - so you could try this if seeing the same issue. Although this, like most of the fixes here, feels hacky and could cause issues in the future when updating your deps.

Approach 4: Manually editing yarn.lock

At this point I thought I would see if I could fix it by updating yarn.lock directly. And that seemed to work.

This is basically the same as deleting your yarn.lock and reinstalling. That step worked ok for me on my fresh Next.js demo app - in a real app you probably won't want to delete your yarn.lock and reinstall as you might have completely different dependency versions installed.

But you can somewhat safely edit your yarn.lock file.

I just needed to do the changes from this diff.

Then it ran fine (and ran fine after adding other deps to package.json and re-installing).

I wouldn't really recommend manually updating yarn.lock, as it should be generated by running yarn.

But in this case the change is minimal, and doesn't seem to get reset after reinstalling/adding new dependencies.

So this is a hacky, but workable fix that you only have to do once.

Approach 5: Switching to pnpm or npm

This can also be resolved by switching to pnpm or npm. The issue is only affecting Yarn.

I did try to figure out why it only affects yarn* but couldn't see a specific reason.

So now we know it is definitely yarn related - it leads to the next approach to fix this issue:

Approach 6: Updating Yarn to v3

This seems to be a yarn specific issue. It doesn't affect NPM

Upgrading yarn with yarn set version 3.6.1, then re-running yarn set it up so yarn test ran ok, although I suspect because it rebuilds yarn.lock.

Other approaches

This is all the lazy workarounds. The real fix would be to figure out the dependency issue and fix it on Github. But for now I thought I'd just write up a short article with these work arounds - maybe this helps someone. Please feel free to drop a comment below if you notice a nicer work around.

Subscribe to my
Full Stack Typescript Developer
newsletter

If you enjoyed my content and want more Full Stack Typescript content, then enter your email below.

I send a few links every couple of weeks, that I personally found useful from around the web.

Focusing on Testing, Typescript, NextJS, and also
engineering soft skills posts.