- 7 minutes read

The nice thing about Jest is it's easy to get started. However, most tutorials tell you about setting up Jest in a default Angular project. I was interested in using Jest in a monorepo, so I had to research.

As far as I can see, there are five roads you can go:

  • Starting with Angular 18 (or even later?), you can use the native support Angular offers. I'm covering this in another article, but as of Angular 17, I don't think it's ready for use in production. Experiment with it and tell the Angular team about it because that's how open source progresses, but don't use it in production.
  • You can configure Jest manually. That's what this article is about.
  • You can use the Briebug schematics to set up Jest in your Angular project or workspace.
  • You can use a plugin like the "Just Jeb" builder (aka @angular-builders/jest. I'm covering this here. You may also want to read Jeb's article.
  • You can use nx. That's almost certainly a good option, but until now, I have always shied away from using nx. I don't want to add another dependency until I need it. That's why I can't tell you much about it.

Setting up the monorepo

Before setting up Jest, let's look at the project structure.

Angular is an opinionated framework, so I set up my monorepo the Angular way. Probably the most future-proof option is using the Angular CLI commands. Using a workspace with subprojects sounds like the way to go:

ng new my-workspace --create-application=false cd my-workspace ng generate application app1 ng generate application app2 ng generate library my-favorite-library

Linking the library to your node_modules folder

Now, you can start implementing code in your library and using it in the applications. Most of the time, this works without further ado. However, I learned it's a good idea to treat the library like a proper library you publish to and download from npm. In particular, Jest requires you to do so.

Most other tutorials tell you to add paths to the tsconfig.json file and module_mappers to the Jest configuration, but that's tedious and poorly documented. Using npm link is easier.

In my projects, I've added a couple of scripts to the package.json to make the library available to the apps:

"scripts": { "postinstall": "npm run build:lib", "build:lib": "ng build -c production my-favorite-library," "postbuild:lib": "cd dist/my-favorite-library && npm link && npm link my-favorite-library"
  • The postinstall script automatically runs after each npm install.
  • build lib is pretty much standard. It compiles the shared library. Angular (and ng-packagr) stores the binaries in a subfolder of the /dist folder.
  • Finally, there's another post script that's called after each npm run build:lib. It makes the compiled binaries of the library available to the apps.

If you want to dive deeper, read my article about npm-link.

Shortcut: use briebug/jest-schematic

You can save a lot of work by using an Angular schematic:

ng add @briebug/jest-schematic

After doing so, you can skip over most of this article and continue with the "migration" section. The following few paragraphs describe how to set up Jest manually. They also provide additional insight that might interest you.

Setting up Jest for the command line

If you prefer to configure Jest manually, the first step is installing Jest and its Angular preset:

npm install jest @types/jest jest-preset-angular --save-dev

If you want to run Jest from the command line, you'll also have to install Jest globally:

npm install -g jest

You can only run Jest from an npm script without the global installation. Which version you prefer is a matter of taste. A side-effect of our monorepo is you always run Jest with a long list of parameters, so I like to store the command as a script in the package.json.

Uninstall Karma and Jasmine

If your installation contains Karma and Jasmine, you can now remove both. By default, Angular 17 doesn't install any test framework. So, this step is primarily an issue if you've got an older project.

npm uninstall karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter karma-coverage jasmine-core rm karma.config.js rm src/test.ts

You can also remove the test section from the angular.json. Jest doesn't read the angular.json, so this configuration part is obsolete.

Configure Jest

After a lengthy trial and error, I learned there's no way to use a single Jest configuration for the entire monorepo. You have to configure Jest for every sub-project of your workspace. This also means you can't run every test with a single command. You have to run Jest for every sub-project individually.

Add the file setup-jest.ts to the root folder of your workspace:

// setup-jest.ts import 'jest-preset-angular/setup-jest';

Now, add a file called config-jest.js to the root folder of every project. Note that this file has to be a JavaScript file (as opposed to the setup-jest file, which may be both JavaScript or TypeScript). Here we go:

// projects/*/config-jest.js module.exports = { preset: 'jest-preset-angular,' setupFilesAfterEnv: ['/../../setup-jest.ts'], testPathIgnorePatterns: [ '/node_modules/', '/dist/', ], globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json', stringifyContentPathRegex: '\\.html$', }, }, testPathIgnorePatterns: [] };

Note that refers to the sub-project folder. So we need to add or modify the tsconfig.spec.json in each sub-project:

// projects/*/tsconfig.spec.json /* To learn more about this file, see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.app.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": [ "jest" ] }, "include": [ "src/**/*.spec.ts", "src/**/*.d.ts" ] }

If your workspace already has such a file, you only need to replace "jasmine" with "jest" in the types array.

Disgression: why do we need the setup-jest.ts?

Why did we add two files? If you've read the jest-config.js carefully, you've probably noticed that the setup-jest.ts is explicetely referenced in that file. So it's not surprising you don't need this file. In fact, the briebug schematic doesn't add this file to your workspace. You can add the Angular preset in the jest-config.js just as well:

// jest-config.js module.exports = { preset: 'jest-preset-angular,' globalSetup: 'jest-preset-angular/global-setup', };

However, the setup-jest.ts file is handy for defining global mocks. It's executed before your tests run. For example, I'm using this feature in one of my projects to disable logging to the console. That's probably an exotic use-case: my project logs too many messages to the console. It messes up the messages of Jest. Monkey-patching window.console fixed that for me:

// setup-jest.ts import 'jest-preset-angular/setup-jest'; global.console = { // or window.console ...console, // uncomment to ignore a specific log level log: jest.fn(), debug: jest.fn(), info: jest.fn(), // warn: jest.fn(), // error: jest.fn(), };

Adding the Jest scripts to the package.json

Now you can run Jest. It gets easier if you add the lengthy commands as scripts to the package.json:

"scripts": { ..., "test:app1": "jest --config projects/app1/jest.config.js", "test:app2": "jest --config projects/app2/jest.config.js", "test:lib": "jest --config projects/my-favorite-library/jest.config.js,"

Now you can run the tests with a slightly shorter command like npm run test:app1.

Error messages at the import statements

If Jest reports an error at the import statement, check your tsconfig.*files module settings. Usually, this means Jest tries to use the CommonJS module system instead of the ESM module system. These settings worked for me:

{ "compilerOptions": { "esModuleInterop": true, "module": "ES2022",

IDE support

Remains IDE support. If you're using Webstorm, that's easy: you're already there. You don't have to configure anything.

The same holds for Jest Runner. That simple plugin allows you to run individual tests or test suites with a single click. Debugging works out of the box, too.

In an earlier article, I described Orta's Jest plugin. This one is more capricious. It has an option to set up monorepos. It's possible to configure it - I managed it once - but after a while, I abandoned it in favor of the more straightforward plugin.

Wrapping it up

Setting up Jest in a monorepo is easy. All you have to know is you must configure Jest for every sub-project. There's no common configuration covering the entire workspace.

I've published a GitHub repository showing you several Jest setups, including monorepos and traditional projects.


Comments