Testing is an essential part of software development, ensuring that your code works as expected and helping to prevent future regressions. TypeScript's strong typing system can catch many errors at compile time, but runtime testing is still necessary to verify logic and behavior. In this chapter, we will explore testing in TypeScript using popular frameworks like Jest and Mocha. We will cover everything from setup and basic tests to advanced testing strategies, ensuring you have a comprehensive understanding of how to effectively test your TypeScript code.
Jest is a popular JavaScript testing framework developed by Facebook. It provides an out-of-the-box testing experience with features like zero configuration, fast iteration speed, and built-in mocking.
npm install --save-dev jest ts-jest @types/jest
jest.config.js
file in the root of your project:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
};
ts-jest
is a TypeScript preprocessor for Jest, enabling you to write your tests in TypeScript.testEnvironment
specifies the environment in which the tests will run.testMatch
defines the glob patterns Jest uses to detect test files.
// src/math.ts
export function add(a: number, b: number): number {
return a + b;
}
// __tests__/math.test.ts
import { add } from '../src/math';
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test
is a Jest function used to define a test case.expect
is an assertion function used to test values.
PASS __tests__/math.test.ts
✓ adds 1 + 2 to equal 3 (3ms)
package.json
to run Jest:
"scripts": {
"test": "jest"
}
npm test
Jest provides utilities to test asynchronous functions, including callbacks, promises, and async/await.
// src/user.ts
export async function fetchUser(id: number): Promise<{ id: number, name: string }> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: 'John Doe' });
}, 1000);
});
}
// __tests__/user.test.ts
import { fetchUser } from '../src/user';
test('fetches user by ID', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'John Doe' });
});
async
and await
are used to handle asynchronous code.toEqual
checks for deep equality.
PASS __tests__/user.test.ts
✓ fetches user by ID (1003ms)
Jest’s mocking capabilities allow you to replace dependencies with mock implementations.
// src/api.ts
export async function getData(): Promise {
// Simulate an API call
return 'real data';
}
// src/service.ts
import { getData } from './api';
export async function fetchData(): Promise {
return await getData();
}
// __tests__/service.test.ts
import { fetchData } from '../src/service';
import * as api from '../src/api';
jest.mock('../src/api');
test('fetches mocked data', async () => {
jest.spyOn(api, 'getData').mockResolvedValue('mocked data');
const data = await fetchData();
expect(data).toBe('mocked data');
});
jest.mock
is used to mock the entire module.jest.spyOn
creates a mock function for a specific method.
PASS __tests__/service.test.ts
✓ fetches mocked data (3ms)
Mocha is a flexible JavaScript testing framework for Node.js, and Chai is an assertion library that works well with Mocha.
Install Mocha, Chai, and the necessary TypeScript dependencies:
npm install --save-dev mocha chai ts-node @types/mocha @types/chai
Create a mocha.opts
file in the root of your project:
--require ts-node/register
--recursive
ts-node/register
allows Mocha to run TypeScript files.--recursive
tells Mocha to include subdirectories.
// src/math.ts
export function multiply(a: number, b: number): number {
return a * b;
}
// test/math.test.ts
import { expect } from 'chai';
import { multiply } from '../src/math';
describe('multiply', () => {
it('should multiply 2 and 3 to get 6', () => {
expect(multiply(2, 3)).to.equal(6);
});
});
describe
groups related tests.it
defines a single test case.expect
is used for assertions.
multiply
✓ should multiply 2 and 3 to get 6
package.json
to run Mocha:
"scripts": {
"test": "mocha 'test/**/*.ts'"
}
npm test
Ensure your test cases are easy to understand and maintain.
Each test should be independent and not rely on the state left by previous tests.
import { beforeEach, afterEach } from 'mocha';
describe('Test Suite', () => {
beforeEach(() => {
// Setup code
});
afterEach(() => {
// Teardown code
});
it('should do something', () => {
// Test code
});
});
Use mocks and stubs to isolate the unit of code being tested from its dependencies.
import sinon from 'sinon';
describe('fetchData', () => {
it('should call getData once', async () => {
const getDataStub = sinon.stub(api, 'getData').resolves('mocked data');
await fetchData();
expect(getDataStub.calledOnce).to.be.true;
getDataStub.restore();
});
});
Testing is an integral part of the development process, and mastering it ensures the reliability and robustness of your code. By leveraging frameworks like Jest and Mocha, you can write efficient and effective tests for your TypeScript projects. This chapter has covered the basics to advanced concepts of testing with Jest and Mocha, along with practical examples and best practices. Happy coding !❤️