JavaScript and TypeScript course Β· Module 11: Testing with Jest
Jest Framework - Basics
In this lesson5
In the previous lesson, a test appeared like a ready-made sensor. But who runs it, collects the results and raises the alarm when something goes wrong? You need a control center that finds the test files by itself, runs them and reports what passed and what did not.
Jest is one of the most popular tools for testing JavaScript and TypeScript. It was created at Facebook (now Meta), and since 2022 it has been developed under the OpenJS Foundation. In our Jurassic Park, Jest will be the main tool for verifying security systems - in this module we rely on version 30.
Installation and Configuration
You install Jest as a development dependency: it goes into the devDependencies section of package.json, because you need it while working on the code, not in production. Here are the commands every project starts with:
1# Installing Jest
2npm install --save-dev jest
3
4# For TypeScript
5npm install --save-dev jest ts-jest @types/jest
6
7# Initializing configuration
8npm init jest@latestThe first command is enough for testing JavaScript, and the second adds the packages for TypeScript - we will come back to them in the lesson on testing TS. The last one starts a wizard that asks a few questions and writes a configuration file. Older versions used the npx jest --init command for this, but Jest 30 removed it. It is also worth adding a "test": "jest" script to package.json - then you can run the whole test battery with npm test.
Test Structure
Every test in Jest consists of three key elements: describe, it (or test), and expect. You do not need to import them, because Jest provides them as global functions in every test file. It finds the files without your help, too: by default it runs everything ending in .test.js or .spec.js (TypeScript variants included), plus files inside a __tests__ folder.
describe - Grouping Tests
describe is a block that groups related tests. Think of it as an enclosure in the park - it groups dinosaurs of the same species. The first argument is the group's name, and the second is a function where you place the tests:
1describe('DinosaurEnclosure', () => {
2 // All tests related to the dinosaur enclosure
3});describe itself checks nothing. It organizes the report: in the results you will see the name DinosaurEnclosure with all the tests from that enclosure underneath. Blocks can be nested, as you will see shortly in the full example.
it / test - Individual Test Case
it and test are interchangeable - both define a single test. The Jest documentation describes it explicitly as an alias of test: it is the same function under two names. Convention: it reads like a sentence in English. You can even mix both forms within one describe:
1describe('DinosaurEnclosure', () => {
2 it('should have a fence', () => {
3 // assertion
4 });
5
6 test('fence is electrified', () => {
7 // assertion
8 });
9});Both tests work identically. The first one reads like the sentence "it should have a fence". I recommend it inside describe and test for standalone tests, but consistency across the whole project matters most.
expect - Assertions
expect is the heart of every test. It compares the actual result with the expected one. The syntax always has two parts: expect(value) wraps the result, and a matcher such as toBe or toEqual states what you expect. createEnclosure is a factory that returns an enclosure object with a name, a capacity and an empty list of dinosaurs:
1describe('DinosaurEnclosure', () => {
2 it('should have correct capacity', () => {
3 const enclosure = createEnclosure('T-Rex Zone', 5);
4 expect(enclosure.capacity).toBe(5);
5 });
6
7 it('should start empty', () => {
8 const enclosure = createEnclosure('Raptor Pen', 10);
9 expect(enclosure.dinosaurs).toEqual([]);
10 expect(enclosure.dinosaurs.length).toBe(0);
11 });
12});When an assertion fails, Jest stops only that one test, shows the expected value (Expected) and the received one (Received), and then moves on to the next test. I will explain in the lesson on matchers why we used toEqual rather than toBe for the empty array.
Complete Example
Let's see a full example of testing a dinosaur management module. First, the production code: createDinosaur creates a specimen at full health, and feedDinosaur raises its health only when the food matches the diet, and never above 100:
1// dinosaur.js - production code
2function createDinosaur(name, species, diet) {
3 return {
4 name,
5 species,
6 diet,
7 health: 100,
8 isAlive: true,
9 };
10}
11
12function feedDinosaur(dinosaur, food) {
13 if (dinosaur.diet === 'carnivore' && food === 'meat') {
14 dinosaur.health = Math.min(100, dinosaur.health + 20);
15 return { success: true, message: 'Dinosaur fed!' };
16 }
17 if (dinosaur.diet === 'herbivore' && food === 'plants') {
18 dinosaur.health = Math.min(100, dinosaur.health + 15);
19 return { success: true, message: 'Dinosaur fed!' };
20 }
21 return { success: false, message: 'Wrong food!' };
22}Now the test file. Two nested describe blocks correspond to the two functions, and each it checks one scenario:
1// dinosaur.test.js - tests
2describe('Dinosaur Management', () => {
3 describe('createDinosaur', () => {
4 it('should create a dinosaur with correct properties', () => {
5 const rex = createDinosaur('Rex', 'T-Rex', 'carnivore');
6
7 expect(rex.name).toBe('Rex');
8 expect(rex.species).toBe('T-Rex');
9 expect(rex.diet).toBe('carnivore');
10 expect(rex.health).toBe(100);
11 expect(rex.isAlive).toBe(true);
12 });
13 });
14
15 describe('feedDinosaur', () => {
16 it('should feed carnivore with meat', () => {
17 const rex = createDinosaur('Rex', 'T-Rex', 'carnivore');
18 rex.health = 70;
19
20 const result = feedDinosaur(rex, 'meat');
21
22 expect(result.success).toBe(true);
23 expect(rex.health).toBe(90);
24 });
25
26 it('should reject wrong food for carnivore', () => {
27 const rex = createDinosaur('Rex', 'T-Rex', 'carnivore');
28
29 const result = feedDinosaur(rex, 'plants');
30
31 expect(result.success).toBe(false);
32 });
33 });
34});In a real project, the test file imports the functions, for example with require('./dinosaur'), and dinosaur.js has to export them. Also notice a gap: the rejected-food test does not check whether Rex's health stayed untouched. That is a good candidate for your first test of your own.
Running Tests
You run tests from the terminal. Here are the four variants you will use most often:
1# Run all tests
2npx jest
3
4# Run a specific file
5npx jest dinosaur.test.js
6
7# Run in watch mode (reacts to changes)
8npx jest --watch
9
10# Run with code coverage reporting
11npx jest --coverageIn --watch mode, every save reruns only the tests related to the changed files (it detects changes through Git), while --coverage produces a coverage report showing which lines of code at least one test executed and which ones no test ever visited.
AAA Pattern (Arrange-Act-Assert)
Every good test should be written according to the AAA pattern: Arrange (prepare), Act (execute), Assert (verify). Three parts separated by a blank line make the test read like an inspection report:
1it('should increase health when fed correctly', () => {
2 // Arrange - create the needed objects
3 const dino = createDinosaur('Brachio', 'Brachiosaurus', 'herbivore');
4 dino.health = 60;
5
6 // Act - execute the tested operation
7 const result = feedDinosaur(dino, 'plants');
8
9 // Assert - verify the result
10 expect(result.success).toBe(true);
11 expect(dino.health).toBe(75);
12});The production code has not changed - only the order of the test has, and thanks to it you can instantly see what is preparation and what is the actual check. My advice: one action in the Act section. If you call three different functions there, you are probably writing three tests at once.
In the next lesson you will get to know the full arsenal of matchers. The lab below runs a simplified simulation of describe, it and expect written in plain JavaScript, because Sandpack does not run the real Jest.
Remember: describe is the enclosure, it is a single inspection, and expect is the sensor that reports PASS or FAIL.
Code for this lesson: index.js
1// Jest - Testing framework basics
2console.log("=== Jurassic Park - Jest Testing System ===\n");
3
4// Simulation of the Jest framework (Sandpack does not run the real Jest)
5// Shows the structure: describe, it/test, expect
6
7// --- Mini Jest implementation ---
8const results = { passed: 0, failed: 0, suites: [] };
9
10function describe(name, fn) {
11 const suite = { name, tests: [] };
12 results.suites.push(suite);
13 const prevSuite = results.currentSuite;
14 results.currentSuite = suite;
15 fn();
16 results.currentSuite = prevSuite;
17}
18
19function it(name, fn) {
20 try {
21 fn();
22 results.currentSuite.tests.push({ name, status: "PASS" });
23 results.passed++;
24 } catch (e) {
25 results.currentSuite.tests.push({ name, status: "FAIL", error: e.message });
26 results.failed++;
27 }
28}
29const test = it; // test and it are interchangeable
30
31function expect(actual) {
32 return {
33 toBe(expected) {
34 if (actual !== expected)
35 throw new Error(`Expected ${expected}, got ${actual}`);
36 },
37 toEqual(expected) {
38 if (JSON.stringify(actual) !== JSON.stringify(expected))
39 throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
40 },
41 toHaveLength(expected) {
42 if (actual.length !== expected)
43 throw new Error(`Expected length ${expected}, got ${actual.length}`);
44 },
45 toBeTruthy() {
46 if (!actual) throw new Error(`Expected truthy, got ${actual}`);
47 },
48 not: {
49 toBe(expected) {
50 if (actual === expected) throw new Error(`Expected not ${expected}`);
51 }
52 }
53 };
54}
55
56// --- Production code ---
57function createDinosaur(name, species, diet) {
58 return { name, species, diet, health: 100, isAlive: true };
59}
60
61function feedDinosaur(dinosaur, food) {
62 if (dinosaur.diet === "carnivore" && food === "meat") {
63 dinosaur.health = Math.min(100, dinosaur.health + 20);
64 return { success: true, message: "Dinosaur fed!" };
65 }
66 if (dinosaur.diet === "herbivore" && food === "plants") {
67 dinosaur.health = Math.min(100, dinosaur.health + 15);
68 return { success: true, message: "Dinosaur fed!" };
69 }
70 return { success: false, message: "Wrong food!" };
71}
72
73// --- Tests ---
74describe("Dinosaur Management", () => {
75 describe("createDinosaur", () => {
76 it("should create a dinosaur with correct properties", () => {
77 const rex = createDinosaur("Rex", "T-Rex", "carnivore");
78 expect(rex.name).toBe("Rex");
79 expect(rex.species).toBe("T-Rex");
80 expect(rex.diet).toBe("carnivore");
81 expect(rex.health).toBe(100);
82 expect(rex.isAlive).toBe(true);
83 });
84
85 it("should create herbivore dinosaur", () => {
86 const brachio = createDinosaur("Brachio", "Brachiosaurus", "herbivore");
87 expect(brachio.diet).toBe("herbivore");
88 expect(brachio.health).toBe(100);
89 });
90 });
91
92 describe("feedDinosaur", () => {
93 it("should feed carnivore with meat (AAA pattern)", () => {
94 // Arrange
95 const rex = createDinosaur("Rex", "T-Rex", "carnivore");
96 rex.health = 70;
97
98 // Act
99 const result = feedDinosaur(rex, "meat");
100
101 // Assert
102 expect(result.success).toBe(true);
103 expect(rex.health).toBe(90);
104 });
105
106 it("should reject wrong food for carnivore", () => {
107 const rex = createDinosaur("Rex", "T-Rex", "carnivore");
108 const result = feedDinosaur(rex, "plants");
109 expect(result.success).toBe(false);
110 });
111
112 it("should feed herbivore with plants", () => {
113 const brachio = createDinosaur("Brachio", "Brachiosaurus", "herbivore");
114 brachio.health = 50;
115 const result = feedDinosaur(brachio, "plants");
116 expect(result.success).toBe(true);
117 expect(brachio.health).toBe(65);
118 });
119 });
120});
121
122// --- Show results ---
123console.log("Test Results:\n");
124results.suites.forEach(suite => {
125 console.log(` ${suite.name}`);
126 suite.tests.forEach(t => {
127 const icon = t.status === "PASS" ? "PASS" : "FAIL";
128 console.log(` [${icon}] ${t.name}`);
129 if (t.error) console.log(` ${t.error}`);
130 });
131});
132console.log(`\nTotal: ${results.passed} passed, ${results.failed} failed`);Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the AAA pattern used in unit testing stand for?
2. What is the purpose of the expect() function in Jest?
Hands-on tasks in the game
- Code editor
Complete the missing assertions in tests for the getDinosaurSpecies function.
- Click in order
Arrange the elements of a basic Jest test:
- Code editor
Write tests for a dinosaur health points system using describe, it, expect.