In this ongoing series, I am putting together a Github Repository Template for my "Go To" front-end tech stack of Next.js, React, TypeScript etc. In the 2nd part of the series, I set up ESLint to work with TypeScript. In this post, I will get Jest Testing to work with TypeScript (and ESLint).
Just want the Code? View the full changeset covered in this post or check out the 0.0.3 release of the repository.
Prerequisites
If you are following along, please complete part 2 of the series. I am immediately picking up where that post left off.
Writing a Simple Function To Test
Thus far, I've only dealt with Next.js page components, which have to live within the /pages directory in the project root. I'll introduce a /src folder to the project root and create a utils folder that will contain add.ts that will export a simple function to test. Using the nested folders will help us ensure that glob patterns are working later with Jest test matching (something worth confirming from experience).
// src/utils/add.ts
// Simple file to help illustrate jest testing
const add = (a: number, b: number): number => a + b;
export default add;
Writing our Test
Even though I have not yet installed Jest, I can still write the test. In a way, I am using Test Driven Design thinking to ensure the test setup works. In the src/utils/ create a file add.test.ts
// src/utils/add.test.ts
import add from './add';
describe('adding two numbers should', () => {
test('return expected when both args are non 0', () => {
expect(add(1, 2)).toEqual(5);
});
test('return 0 when both args are 0', () => {
expect(add(0, 0)).toEqual(5);
});
test('return expected when both args trigger floating point error', () => {
expect(add(0.1, 0.2)).toEqual(5);
});
});
Note: These tests are expected to fail when run as the expected value is 5. This is Test Driven Design thinking. Once they actually fail, I know they are being run and fixing them is trivial. I'll write a series about unit testing later.
If you are following along, and have ESLint set up properly from the previous post, you should see some errors in your IDE highlighted as per below. As per the previous post, I am using VSCode.
This is due to TypeScript not knowing the types for describe, test, and expect. I need to install Jest and Jest Type Definitions to clean that up.
Installing Jest and Types
To install Jest and the associated Types, simply run the following in the terminal
npm install --save-dev jest @types/jest
Once installed, the ESLint errors go away in the IDE
Running Tests with Jest
I have some code, I have a test for that code, and I installed Jest. However, I can't actually run the tests yet. Let's set that up next.
Update the default test script in package.json from
"test": "echo \"Error: no test specified\" && exit 1"
to
"test": "jest --collectCoverage true",
I can now run the tests by typing the following in the command line
npm run test
This complains with an error: The reason for the error is that Jest doesn't know how to deal with TypeScript yet. Even though I have installed the Types, Jest needs some additional dependencies and configuration.
Configuring Jest for TypeScript
I need to tell Jest how to deal with TypeScript.
Run the following in the terminal to install TypeScript support for Jest
npm install --save-dev ts-jest
Once installed, add the following top level directive to the package.json
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".test.ts(x?)$",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.{ts,tsx}"
],
"moduleNameMapper": {
"~/(.*)$": "<rootDir>/$1"
},
"coverageDirectory": "../coverage",
"testEnvironment": "jsdom",
"resetMocks": true
}
Note: This config could live in a separate jestconfig.json file. However, jest supports putting config in the package.json so why clutter up the project directory?
Jest is now ready to run the tests written in TypeScript:
npm run test
The tests failed, but they ran! I'll update the tests to pass by changing the expected value to what logically makes sense. Note that nasty floating point test...
import add from './add';
describe('adding two numbers should', () => {
test('return expected when both args are non 0', () => {
expect(add(1, 2)).toEqual(3);
});
test('return 0 when both args are 0', () => {
expect(add(0, 0)).toEqual(0);
});
test('return expected when both args trigger floating point error', () => {
expect(add(0.1, 0.2)).toEqual(0.30000000000000004);
});
});
Running the tests again:
Celebrate!
Separating Unit and Integration Tests
I am a big fan of Test Driven Design. I'm also a big fan of separating unit tests from other slower types of tests. Personally, if running unit tests is slow, I believe people (myself included) skip TDD and/or skip writing tests all together. For that reason, I tend to prefer unit tests to be very simple and ran independently of other tests. Python's nose test runner used to have a simple class tag approach to this, but we can leverage Jest's filename matching to accomplish the same thing.
I will introduce two additional scripts to the package.json:
"unit": "jest --testRegex '(?<!integration\\.)test\\.ts(x?)$'",
"integration": "jest --testRegex 'integration\\.test\\.ts(x?)$'",
With these in place, I have 3 test commands:
npm run unit
will run all unit test (those not ending inintegration.test.ts(x?)
)npm run unit
will run all the slower integration tests.npm run test
will run all tests AND generate a coverage report (which isn't necessary normally during normal dev)
I've previously written about this topic on my personal blog. If you would like more explanation, give it a read.
Closing Thoughts
At this point, the Github Template repository contains a Next.js application written in TypeScript that obeys airbnb formatting rules without any errors, and has Jest testing set up. View the full changeset covered in this post or check out the 0.0.3 release of the repository.
This is a great starting point for most projects, but I'm going to do a bit more in part 4 of the series by adding module aliases.
Discussion Topic
Have you been using Tap instead of Jest? Do you like it? Post in the comments.
Image Credit: Photo by Ylanite Koppens from Pexels