A guide through The Wild Wild West of setting up a mono repo — Part 2: Adding Jest to our mono repo

Auke ten Hoopen
Feb 11 · 4 min read

This is part two of our mono repo setup. In the first part, we created a mono repo with Lerna and TypeScript. In this part, we will focus on adding Jest to our setup.

You can find the full example and more in the template repo:

https://github.com/ahoopen/typescript-mono-repo

Installation

Let's install our dependencies. Use one of the following commands.

NPM

npm install --save-dev jest ts-jest

Yarn

yarn add --dev jest ts-jest

After this is done installing, let's create our Jest config file. By default, Jest can run without any config files, but it will not compile files. To make it transpile TypeScript with ts-jest , we will need to create a configuration file that will tell Jest to use a ts-jest preset. ts-jest can create the configuration file for you automatically.

NPM

npx ts-jest config:init

Yarn

yarn ts-jest config:init

This command will create a jest.config.js file in our root. This config will inform Jest about how to handle files correctly.

Testing packages

Before we can run our tests we first need to create them. Let’s create a simple function for adding numbers. Our add function, beautiful right? :)

packages/web/src/add.ts

export const add = (a: number, b: number) => {
return a + b;
};

And the unit test for it:

packages/web/src/add.spec.ts

import { add } from "./add";describe("add function", () => {
it("should add two number together", () => {
const result = add(10, 5);
expect(result).toBe(15);
});
});

Now that we have our add function and unit test in place, we can start adding our test command — yarn jest — to our package. Let's add it to our webpackage.

{
"name": "@ahoopen/web",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"directory": "packages/web",
"url": "https://github.com/ahoopen/typescript-mono-repo.git"
},
"scripts": {
"clean": "rimraf dist",
"prepack": "yarn build",
"build": "yarn clean && yarn compile",
"compile": "tsc && cp \"./package.json\" ./dist/",
"test": "yarn jest",
"lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0"
},
"dependencies": {
"react": "16.13.1"
}
}

Now run yarn test in the root of our repository. It will run tests in all of our packages. So far nothing crazy, I know. Testing in a mono repo gets a lot harder when you have multiple packages and some of them depend on each other.

Dependency on own mono repo package

What if we want to enhance ouradd method with a util method from our @ahoopen/utilspackage. This is a bit of a contrived example. It doesnt make any sense but its only to illustrate the problem :)

import { multiplyByTwo } from "@ahoopen/utils";export const add = (a: number, b: number) => {
console.log(multiplyByTwo(a));
return a + b;
};

Next step is to add the package to our dependencies.

"dependencies": {
"react": "16.13.1",
"@ahoopen/utils": "1.0.0"
},

Now we only have to run yarn test and we are done, right? Let's see…

Test suite failed to runCannot find module '@ahoopen/utils' from 'packages/web/src/add.spec.ts'

Tests fail…but why? The reason why this fails is, that in our TypeScript tsconfig we have mapped our package @ahoopen/utils to @ahoopen/utils/src

Our tsconfig

{
"compilerOptions": {
"jsx": "react",
"baseUrl": "./packages",
"paths": {
"@ahoopen/core/*": ["core/src/*"],
"@ahoopen/web/*": ["web/src/*"],
"@ahoopen/utils/*": ["utils/src/*"]
}
}
}

Why don’t we remove that mapping? Then the tests also work immediately.

Well, this mapping helps us to easily navigate through all the different packages and when we make a code change, it will be picked up in all packages without needed to build them first. But the downside with this approach is that Jest can’t resolve the packages.

Luckily we have a solution to this problem.

Path mapping in Jest

In our Jest config, we have to set up the “moduleNameMapper” correctly to fix this issue.

jest.config.js

const path = require('path');
const { lstatSync, readdirSync } = require('fs');
// get listing of packages in the mono repo
const basePath = path.resolve(__dirname, 'packages');
const packages = readdirSync(basePath).filter(name => {
return lstatSync(path.join(basePath, name)).isDirectory();
});
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
...packages.reduce(
(acc, name) => ({
...acc,
[`@ahoopen/${name}(.*)$`]:
`<rootDir>/packages/./${name}/src/$1`,
}),
{},
),
},
};

Oke, so what's happing here? We read the packages folder to get every package name. Then we automatically generate a moduleNameMapper. Which boils down to the same mapping as with the tsconfig . We map the import path to the package src directory.

This will be the generated output:

moduleNameMapper: {
'@ahoopen/core(.*)$': '<rootDir>/packages/./core/src/$1',
'@ahoopen/utils(.*)$': '<rootDir>/packages/./utils/src/$1',
'@ahoopen/web(.*)$': '<rootDir>/packages/./web/src/$1'
},

If we run yarn test again, our tests pass :)

PASS  packages/web/src/add.spec.ts
add function
✓ should add two number together (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.711 s, estimated 3 s
Ran all test suites.
✨ Done in 2.11s.

Conclusion

We have added Jest to our mono repo. We can now run Jest in all of our TypeScript packages. To get it all working nicely together we needed to add a moduleNameMapper to our jest config. By automatically reading our package folder. We don’t have to keep adjusting our jest config when we create a new package.

The next part we will set up Github actions for releasing our packages. Stay tuned!

AH Technology

Sharing our knowledge when building the world greatest Food & Tech company. #ahtechnology

AH Technology

Albert Heijn, part of Ahold Delhaize, is the number one food retailer in the Netherlands. We’ve been inspiring customers and building trust for more than 130 years. We making better food accessible to everyone — and making shopping fit the way people live today. #ahtechnology

Auke ten Hoopen

Written by

Lead Frontend developer @ Albert Heijn

AH Technology

Albert Heijn, part of Ahold Delhaize, is the number one food retailer in the Netherlands. We’ve been inspiring customers and building trust for more than 130 years. We making better food accessible to everyone — and making shopping fit the way people live today. #ahtechnology