this is something I do just rarely enough that it's a pain in the butt. Next time Google is going to find this article and we'll know what to do 🤞
Jest is a testing framework from Facebook. Fast becoming an industry standard thanks to a good balance between flexibility and batteries included. No need to cobble together a bunch libraries into a bespoke framework nobody outside your team understands.
cough sinon, mocha, chai, karma cough
Using plain Jest on a TypeScript codebase has rough edges. Your code compiles before testing, which means you:
- Can't use types in your tests
- Don't get type errors in your tests
- Get error messages about compiled instead of source code
- Have to debug compiled JavaScript and manually relate back to TypeScript source code
The goal: full TypeScript support in your tests, type checking when running tests, meaningful error messages. 😍
We'll get there with ts-jest, a Jest transformer that enables Jest to understand TypeScript.
You can see the full repository for this code on GitHub.
Initial setup
We start with an empty-ish repository after running git init
and yarn init
. Or an existing codebase. Either works :)
For the initial setup we can use ts-jest's install documentation
yarn add -D jest typescript ts-jest @types/jest ts-node yarn ts-jest config:init
This installs jest and ts-jest, TypeScript (if you don't have it yet), and type definitions for jest so TypeScript knows what's available. You'll need ts-node to support TypeScript-based configuration later.
config:init
gives you a default config file like this:
// jest.config.js
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
}
Make tests runnable
If you don't have it yet, add this section to your package.json
:
{
"scripts": {
"test": "jest"
}
}
You can now run yarn test
to fire up Jest and run all your tests. Jest doesn't require any configuration to find your tests. Goes through the whole project and looks for files that look like they're tests.
This will bite us later, but it's lovely.
Configure TypeScript
If you haven't yet, you'll need to configure TypeScript. I like to use this file as a sane default, it's traveled with me through many projects:
// tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"types": ["node", "jest"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": ".",
"noFallthroughCasesInSwitch": true,
"outDir": "./dist"
},
"compileOnSave": true,
"include": ["."]
}
The key parts are:
"types": ["node", "jest"]
, tells TypeScript to assume node and jest types are globally available. Make sure you includejest
."outDir": "./dist"
I like to put compiled files in/dist
. Best add dist this to.gitignore
so you don't add compiled files to git"forceConsistentCasingInFileNames": true
if you work on a Mac and deploy to anything but a Mac, this is going to save you lots of frustration. Ask me how I know 😖
Tell TypeScript to ignore Jest files
Jest encourages you to put test files next to the code you're testing. Makes tests easier to find and cleans up imports.
What's better?
this:
// tests/api/v2/importantBlob/functions.test.ts
import functionBeingTested from "../../../src/api/v2/importantBlob/functions"
or this:
// src/api/v2/importantBlob/__tests__/functions.test.ts
import functionBeingTested from "../functions"
🤔
Let's agree colocating tests and code is better.
But now you have a problem – when TypeScript builds your code, all those test files end up in dist/
. Bloats your builds and in some cases confuses Jest about which files to run and breaks testing.
Solution: a special config for builds:
// tsconfig.build.json
{
"extends": "./tsconfig",
"exclude": ["**/*.test.*", "**/__mocks__/*", "**/__tests__/*"]
}
This configuration tells TypeScript to exclude files that look like tests. It has to be separate otherwise ts-jest
won't see your test files 🙃
To use tsconfig.build.json
, add this to your package.json
file or build process:
{
"scripts": {
"test": "jest",
"build": "tsc -p tsconfig.build.json"
}
}
Now when you run yarn build
, typescript uses the special tsconfig.build.json
configuration.
Improved Jest config
I didn't like having a JavaScript file floating around my pure and clean wonderful codebase just to configure Jest. Kill the default and make it TypeScript 💪
// jest.config.ts
import type { Config } from "@jest/types"
const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
verbose: true,
automock: true,
}
export default config
Much better.
Same ts-jest
preset and node
test environment as before, added verbose
and automock
. Automock is nice because it tells Jest to automatically create a mocked version of any imported code.
Great for ensuring a clean environment for every test. And you can define specific behavior when you need it. We'll talk about that another time.
Write a test
To ensure everything's working, we write a quick test.
Put a silly function in src/silly.ts
:
export function sillyFunction() {
return 4 // chosen by fair dice throw, guaranteed to be random
}
And a test for this function in src/__tests__/silly.test.ts
:
// requireActual ensures you get the real file
// instead of an automock
// we use import type and <typeof ...> to still get types
import type * as Silly from "../silly"
const { sillyFunction } = jest.requireActual<typeof Silly>("../silly")
describe("silly function", () => {
test("guaranteed random", () => {
expect(sillyFunction()).toBe(4)
})
})
// required with this small example
// to make the isolatedModules config happy
export {}
We have to use jest.requireActual
because of the earlier automock: true
configuration. This loses type information for the code we're testing, so we help TypeScript by doing import type
and passing that type to jest.requireActual
with <>
.
You keep full type hints in your test code:
Run yarn test
and you get a successful test run:
Happy testing ❤️
Cheers,
~Swizec
PS: this is the setup for an article coming next week, here's a sneak peek:
I give up. A simple in-memory database to use for integration testing is clearly too much to ask for
— Swizec Teller (@Swizec) October 16, 2021
how has nobody solved this yet? it's 2021 damn it pic.twitter.com/cBy14AWcaK
think I have a solution but needed to write this part first 🤞
Continue reading about How to configure Jest with TypeScript
Semantically similar articles hand-picked by GPT-4
- TypeScript for serverless lambda backends 👌
- Learn TypeScript in 5 minutes
- Mocking and testing fetch requests with Jest
- TypeScript's biggest flaw and how you can use ducks to fix it
- The efficacy of TypeScript
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️