test
'Jest' is a widely-used JavaScript testing framework. It is commonly used for unit and integration testing in Node.js projects, and comes with built-in mocking capabilities and code coverage measurement. Since Node.js v18, a built-in test runner (node:test) is also available.
Basic Jest Syntax
// describe: group tests together
describe('group name', function() {
// it or test: individual test case
it('test description', function() {
// expect: assertion (verify expected value)
expect(actualValue).toBe(expectedValue);
});
});
Key Matchers (expect)
| Matcher | Overview |
|---|---|
toBe(value) | Compares using strict equality (===). Used for primitive values. |
toEqual(value) | Recursively compares the contents of objects or arrays. Checks by value, not reference. |
toBeTruthy() | Verifies that the value is truthy. |
toBeFalsy() | Verifies that the value is falsy. |
toBeNull() | Verifies that the value is null. |
toBeUndefined() | Verifies that the value is undefined. |
toContain(item) | Verifies that an array contains the specified element, or that a string contains a substring. |
toThrow(error) | Verifies that a function throws an exception. |
toHaveBeenCalled() | Verifies that a mock function was called at least once. |
toHaveBeenCalledWith(...args) | Verifies that a mock function was called with the specified arguments. |
not.toBe(value) | Prefixing with not creates a negated assertion. |
describe / it — Writing Basic Tests
'describe()' groups tests together, and 'it()' or 'test()' defines individual test cases. Extracting the logic under test into a function makes the code easier to test.
calc.js
// Export the functions under test
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = { add: add, multiply: multiply, divide: divide };
calc.test.js
var calc = require('./calc');
// Use describe to create test groups
describe('calc module tests', function() {
// Tests for addition
describe('add()', function() {
it('can add two positive integers', function() {
// Attack power calculation for Yagami Iori
expect(calc.add(100, 50)).toBe(150);
});
it('can add negative numbers', function() {
expect(calc.add(-30, 80)).toBe(50);
});
});
// Tests for multiplication
describe('multiply()', function() {
it('can multiply two numbers', function() {
// Combo damage calculation for Kusanagi Kyo
expect(calc.multiply(25, 4)).toBe(100);
});
});
// Tests for division (including error cases)
describe('divide()', function() {
it('can divide normally', function() {
expect(calc.divide(100, 4)).toBe(25);
});
it('throws an error when dividing by zero', function() {
// Terry Bogard special move input error
expect(function() {
calc.divide(100, 0);
}).toThrow('Cannot divide by zero');
});
});
});
npx jest calc.test.js
PASS ./calc.test.js
calc module tests
add()
✓ can add two positive integers (2 ms)
✓ can add negative numbers
multiply()
✓ can multiply two numbers
divide()
✓ can divide normally
✓ throws an error when dividing by zero
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
beforeEach / afterEach — Setup and Cleanup
To run shared logic before and after each test case, use 'beforeEach()' and 'afterEach()'. This is important for keeping tests independent from one another.
fighter.test.js
// Simple class under test (ES5 style)
function Fighter(name, hp) {
this.name = name;
this.hp = hp;
this.log = [];
}
Fighter.prototype.takeDamage = function(amount) {
this.hp = Math.max(0, this.hp - amount);
this.log.push('damage: ' + amount);
};
Fighter.prototype.isDefeated = function() {
return this.hp === 0;
};
// Test suite
describe('Fighter class tests', function() {
var iori;
// Create a fresh instance before each test
beforeEach(function() {
iori = new Fighter('Yagami Iori', 1000);
});
// Cleanup after each test if needed
afterEach(function() {
// Add cleanup logic here if necessary
});
it('HP decreases when taking damage', function() {
iori.takeDamage(300);
expect(iori.hp).toBe(700);
});
it('HP does not go below zero even with excessive damage', function() {
iori.takeDamage(1500);
expect(iori.hp).toBe(0);
});
it('HP reaching zero triggers defeat', function() {
iori.takeDamage(1000);
expect(iori.isDefeated()).toBe(true);
});
it('damage log is recorded', function() {
iori.takeDamage(200);
iori.takeDamage(300);
// Verify array contents
expect(iori.log).toEqual(['damage: 200', 'damage: 300']);
});
});
npx jest fighter.test.js
PASS ./fighter.test.js
Fighter class tests
✓ HP decreases when taking damage (1 ms)
✓ HP does not go below zero even with excessive damage
✓ HP reaching zero triggers defeat
✓ damage log is recorded
Tests: 4 passed, 4 total
jest.fn() — Mock Functions
To isolate external dependencies (databases, APIs, etc.) from tests, create mock functions with 'jest.fn()'. Call history and return values can be verified and controlled.
mock_function.test.js
// Testing callbacks with mock functions
describe('jest.fn() mocking', function() {
it('verifies that a callback is called correctly', function() {
// Create a mock function (a dummy function that records calls)
var onWin = jest.fn();
// Function under test: calls callback on victory
function battle(fighter, opponent, callback) {
if (fighter.power > opponent.power) {
callback(fighter.name + ' wins!');
}
}
var kyo = { name: 'Kusanagi Kyo', power: 1000 };
var iori = { name: 'Yagami Iori', power: 800 };
battle(kyo, iori, onWin);
// Verify the callback was called once
expect(onWin).toHaveBeenCalledTimes(1);
// Verify it was called with the correct argument
expect(onWin).toHaveBeenCalledWith('Kusanagi Kyo wins!');
});
it('mockReturnValue can control mock return values', function() {
var getRank = jest.fn();
// Set values to return on each call
getRank.mockReturnValueOnce('S').mockReturnValueOnce('A').mockReturnValue('B');
expect(getRank()).toBe('S'); // 1st call
expect(getRank()).toBe('A'); // 2nd call
expect(getRank()).toBe('B'); // 3rd call and beyond
expect(getRank()).toBe('B');
});
it('call history can be verified', function() {
var logAttack = jest.fn();
logAttack('Mai Shiranui', 'Shiranui Dan');
logAttack('Ryo Sakazaki', 'Haoh Shoukouken');
// Verify it was called twice in total
expect(logAttack).toHaveBeenCalledTimes(2);
// Verify the arguments of each call
expect(logAttack.mock.calls[0]).toEqual(['Mai Shiranui', 'Shiranui Dan']);
expect(logAttack.mock.calls[1]).toEqual(['Ryo Sakazaki', 'Haoh Shoukouken']);
});
});
npx jest mock_function.test.js
PASS ./mock_function.test.js
jest.fn() mocking
✓ verifies that a callback is called correctly (2 ms)
✓ mockReturnValue can control mock return values
✓ call history can be verified
Tests: 3 passed, 3 total
node:test — Node.js Built-in Test Runner
Since Node.js v18, a test runner is built into the standard library and can be used without installing external packages like Jest. Import the node:test module to use it. The syntax is similar to Jest, but it runs with the node command alone.
builtin_test.mjs
// Use the Node.js built-in test runner (v18+)
import test from 'node:test';
import assert from 'node:assert/strict';
// Basic test
test('addition works correctly', function(t) {
assert.equal(1 + 1, 2);
assert.equal(100 + 200, 300);
});
// Subtests (equivalent to describe)
test('Yagami Iori skill tests', async function(t) {
await t.test('flame claw damage calculation', function() {
var damage = 120 * 3; // 3-hit combo
assert.equal(damage, 360);
});
await t.test('50% damage reduction on guard', function() {
var damage = 360;
assert.equal(Math.floor(damage * 0.5), 180);
});
});
// Verify that an exception is thrown
test('division by zero throws an exception', function() {
function divide(a, b) {
if (b === 0) throw new Error('Division by zero error');
return a / b;
}
assert.throws(function() {
divide(10, 0);
}, { message: 'Division by zero error' });
});
node --test builtin_test.mjs
▶ addition works correctly
✓ addition works correctly (0.5ms)
▶ Yagami Iori skill tests
▶ flame claw damage calculation
✓ flame claw damage calculation (0.2ms)
▶ 50% damage reduction on guard
✓ 50% damage reduction on guard (0.1ms)
✓ Yagami Iori skill tests (1.2ms)
▶ division by zero throws an exception
✓ division by zero throws an exception (0.3ms)
ℹ tests 4
ℹ pass 4
ℹ fail 0
Installing and Configuring Jest
Jest is installed via npm or Yarn. After installing, register the test command in the scripts field of package.json so it can be run with npm test.
npm install --save-dev jest
package.json
{
"scripts": {
"test": "jest"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
Name test files *.test.js or *.spec.js and Jest will detect them automatically. Placing test files in a __tests__ directory is also a common approach.
Common Mistakes
Forgetting await in async tests
When writing assertions for async functions without await, the test completes before the Promise resolves, causing the test to always pass. Cases that should fail will go undetected, resulting in incorrect test results.
async function fetchHp(name) {
return new Promise(function(resolve) {
setTimeout(function() { resolve(1000); }, 100);
});
}
// NG: without await, the test completes immediately and always passes
it('can fetch HP (NG)', function() {
var promise = fetchHp('Yagami Iori');
expect(promise).resolves.toBe(1000);
});
// OK: use async/await to wait until the Promise resolves
it('can fetch HP (OK)', async function() {
var hp = await fetchHp('Yagami Iori');
expect(hp).toBe(1000);
});
Alternatively, returning the Promise to Jest with return achieves the same waiting behavior.
Forgetting to restore mocks
After setting up a mock with jest.spyOn(), failing to call jest.restoreAllMocks() in afterEach or afterAll causes the mock to affect other test cases, producing unexpected results.
describe('mock cleanup', function() {
// Restore mocks after each test with afterEach
afterEach(function() {
jest.restoreAllMocks();
});
it('mocking console.log', function() {
var spy = jest.spyOn(console, 'log').mockImplementation(function() {});
console.log('Kusanagi Kyo output');
expect(spy).toHaveBeenCalledWith('Kusanagi Kyo output');
});
it('console.log is restored in this test', function() {
console.log('Yagami Iori normal output');
});
});
Setting restoreMocks: true in jest.config.js automatically restores mocks after each test.
Overview
Jest is one of the most widely-used testing frameworks in Node.js projects. Its simple structure of grouping tests with describe() and defining individual cases with it()/test() makes it easy to write readable test code.
Using the mock feature (jest.fn()) isolates external dependencies and improves test speed and reproducibility. Using beforeEach()/afterEach() to share setup and teardown logic across tests prevents state pollution between test cases.
The node:test module built into Node.js v18+ runs without any external packages, making it useful for projects that want to minimize dependencies or for testing small scripts. For full-featured projects, Jest's coverage measurement, snapshot testing, and other rich features are useful.
If you find any errors or copyright issues, please contact us.