Right, let’s get your TypeScript playing nicely with Jest. This is one of those things that feels like it should be a one-line setup, but the reality is, we have to make a few choices. The core problem is simple: Jest is a JavaScript testing framework. It expects .js files. You’re giving it .ts files. It looks at you like you’ve just handed it a fish and asked it to do your taxes.

We need a translator. And you’ve got two main contenders for the job: ts-jest and babel-jest. One is a specialist, the other is a generalist. Let’s break them down.

ts-jest: The TypeScript Native

ts-jest is the most straightforward choice. It’s a Jest transformer written specifically for TypeScript. It doesn’t mess around; it takes your TypeScript code, uses the TypeScript compiler (tsc) to turn it into JavaScript, and feeds that directly to Jest. This means it respects your tsconfig.json file to the letter.

The beauty of ts-jest is its fidelity. Because it uses the actual TypeScript compiler, your tests are running against the exact same JavaScript that tsc would produce. If your code compiles with tsc, it will almost certainly run with ts-jest. It also handles TypeScript’s path mapping beautifully.

Installation is a breeze:

npm install --save-dev jest ts-jest @types/jest typescript

Then, generate a basic configuration file. This is the best way to go, as it sets sensible defaults.

npx ts-jest config:init

This creates a jest.config.js file that looks something like this:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

The key here is preset: 'ts-jest', which is essentially a bundle of Jest configuration options pre-set to work with ts-jest. Now, you can just run npx jest and it will find and run your .test.ts or .spec.ts files.

babel-jest: The JavaScript Powerhouse

Now, let’s talk about babel-jest. Its approach is different. Instead of using the TypeScript compiler, it uses Babel to strip away the TypeScript types, leaving plain JavaScript behind for Jest to execute.

First, the install:

npm install --save-dev jest @babel/preset-typescript @babel/preset-env @types/jest babel-jest

You’ll need a babel.config.js file to tell Babel what to do:

module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript',
  ],
};

And your jest.config.js can be much simpler, as babel-jest is often the default transformer, but it’s good to be explicit:

module.exports = {
  testEnvironment: 'node',
  transform: {
    '^.+\\.[tj]sx?$': 'babel-jest',
  },
};

Here’s the critical thing to understand: Babel does not type-check your code. It just transplies it. It’s like a very competent editor who removes all the red squiggly underlines from your document without actually checking if the words are spelled correctly. This means your tests might run beautifully even if you have glaring type errors. This is a feature (it’s faster) and a massive pitfall (you can have false confidence) all rolled into one.

So, Which One Should You Use?

This isn’t a cop-out answer: it depends.

  • Use ts-jest if: You want your tests to be a true type-checking gate. You want the safety net. The performance hit is generally negligible for most projects. This is my default recommendation for most projects because it does the right thing by default.

  • Use babel-jest if: You are in a complex JavaScript ecosystem already using Babel (e.g., with React) and you want consistency. Your type checking is handled as a separate step (e.g., tsc --noEmit in your CI pipeline) and you want the absolute fastest test execution possible.

The “Babel for speed, ts-jest for safety” mantra is a good one. Personally, I lean towards safety. I’d rather my tests fail because of a type error than pass in spite of one. It defeats the whole purpose of using TypeScript.

The Crucial Pitfall: ESLint and Jest Globals

Whichever path you choose, there’s one annoying little “gotcha” that will bite you immediately. Your test files use Jest globals like describe, it, expect, and beforeEach. TypeScript, rightfully, has no idea what these are.

You’ve installed @types/jest, right? Good. But you might still get ESLint errors saying these globals are undefined. That’s because ESLint doesn’t know about Jest’s testing environment in this file.

The fix is to tell ESLint explicitly. In your .eslintrc.js (or equivalent), add:

module.exports = {
  // ... your other configs
  env: {
    jest: true,
  },
};

This one little line saves you from a world of pointless frustration. You’re welcome. Now go write some tests that actually work.