with testing frameworks (Jest/Mocha)
Using Testing Frameworks (Jest/Mocha) in TypeScript
Choosing the right testing framework for a TypeScript project is crucial for code quality. Jest and Mocha are two mainstream options, each with its own characteristics, but both integrate well with TypeScript.
Basic Configuration and Usage of Jest
Jest is a testing framework developed by Facebook, featuring built-in assertion libraries and mock functionality. To use it in a TypeScript project, install the following dependencies:
npm install --save-dev jest ts-jest @types/jest
Configure jest.config.js
:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
};
Example test:
// math.test.ts
import { add } from './math';
describe('math operations', () => {
it('adds two numbers correctly', () => {
expect(add(1, 2)).toBe(3);
});
it('handles decimal numbers', () => {
expect(add(0.1, 0.2)).toBeCloseTo(0.3);
});
});
Basic Configuration and Usage of Mocha
Mocha is a more flexible testing framework that requires additional libraries. Install the dependencies:
npm install --save-dev mocha chai @types/mocha @types/chai ts-node
Configure .mocharc.json
:
{
"extension": ["ts"],
"spec": "src/**/*.test.ts",
"require": "ts-node/register"
}
Example test:
// math.test.ts
import { expect } from 'chai';
import { multiply } from './math';
describe('math operations', () => {
it('multiplies two numbers', () => {
expect(multiply(2, 3)).to.equal(6);
});
it('returns NaN with invalid inputs', () => {
expect(multiply('a' as any, 2)).to.be.NaN;
});
});
Comparison of Asynchronous Testing
Jest handles asynchronous testing more concisely:
// Jest async test
test('fetches user data', async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty('id', 1);
});
Mocha requires explicitly returning a Promise or using a done
callback:
// Mocha async test
it('fetches user data', async () => {
const user = await fetchUser(1);
expect(user).to.have.property('id', 1);
});
// Or using a done callback
it('fetches user data', (done) => {
fetchUser(1).then(user => {
expect(user).to.have.property('id', 1);
done();
});
});
Differences in Mock Functionality
Jest has a built-in powerful mock system:
// Jest mock example
jest.mock('./api');
import { fetchData } from './api';
test('mocks API call', async () => {
(fetchData as jest.Mock).mockResolvedValue({ data: 'mock' });
const result = await callApi();
expect(result).toEqual({ data: 'mock' });
});
Mocha requires additional libraries like sinon:
// Mocha + sinon mock example
import sinon from 'sinon';
import { fetchData } from './api';
describe('API tests', () => {
let fetchStub: sinon.SinonStub;
beforeEach(() => {
fetchStub = sinon.stub(fetchData).resolves({ data: 'mock' });
});
it('mocks API call', async () => {
const result = await callApi();
expect(result).to.deep.equal({ data: 'mock' });
expect(fetchStub.calledOnce).to.be.true;
});
});
Test Coverage Reports
Jest has built-in coverage tools with simple configuration:
// jest.config.js
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['/node_modules/'],
};
Mocha requires nyc
to generate coverage:
npm install --save-dev nyc
Configure package.json
:
{
"scripts": {
"test:coverage": "nyc mocha"
},
"nyc": {
"extension": [".ts"],
"include": ["src/**/*.ts"],
"reporter": ["text", "html"]
}
}
TypeScript-Specific Testing Scenarios
Testing generic functions:
// Testing generic functions
function identity<T>(arg: T): T {
return arg;
}
describe('identity function', () => {
it('returns same value for number', () => {
expect(identity(42)).toBe(42);
});
it('returns same value for string', () => {
expect(identity('test')).toBe('test');
});
});
Testing class methods:
class Calculator {
private value: number;
constructor(initialValue = 0) {
this.value = initialValue;
}
add(num: number): this {
this.value += num;
return this;
}
getResult(): number {
return this.value;
}
}
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
it('chains methods correctly', () => {
const result = calc.add(5).add(3).getResult();
expect(result).toBe(8);
});
});
Testing React Components
Jest with @testing-library/react for testing React components:
import React from 'react';
import { render, screen } from '@testing-library/react';
import Button from './Button';
describe('Button component', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Performance Testing Comparison
Jest performance testing example:
test('performance test', () => {
const start = performance.now();
heavyCalculation();
const end = performance.now();
expect(end - start).toBeLessThan(100); // Should complete within 100ms
});
Mocha performance testing typically uses benchmark.js:
import Benchmark from 'benchmark';
describe('Performance', () => {
it('measures function speed', function() {
this.timeout(10000); // Extend timeout
const suite = new Benchmark.Suite;
suite.add('heavyCalculation', () => heavyCalculation())
.on('cycle', (event: Benchmark.Event) => {
console.log(String(event.target));
})
.run();
});
});
Custom Assertion Extensions
Adding custom matchers in Jest:
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
return {
message: () => `expected ${received} ${pass ? 'not ' : ''}to be within ${floor}-${ceiling}`,
pass,
};
},
});
test('custom matcher', () => {
expect(10).toBeWithinRange(5, 15);
});
Mocha using chai plugins:
import chai from 'chai';
chai.use(function(chai, utils) {
const Assertion = chai.Assertion;
Assertion.addMethod('within', function(floor, ceiling) {
this.assert(
this._obj >= floor && this._obj <= ceiling,
`expected #{this} to be within ${floor} and ${ceiling}`,
`expected #{this} not to be within ${floor} and ${ceiling}`
);
});
});
it('custom assertion', () => {
expect(10).to.be.within(5, 15);
});
Test Environment Cleanup
Jest cleanup hooks:
let db: Database;
beforeAll(async () => {
db = await connectToTestDB();
});
afterAll(async () => {
await db.close();
});
afterEach(async () => {
await db.clear();
});
Mocha cleanup approach:
let db: Database;
before(async () => {
db = await connectToTestDB();
});
after(async () => {
await db.close();
});
afterEach(async () => {
await db.clear();
});
Testing HTTP APIs
Jest testing Express APIs:
import request from 'supertest';
import app from '../app';
describe('GET /users', () => {
it('responds with json', async () => {
const response = await request(app)
.get('/users')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveLength(3);
});
});
Mocha testing Koa APIs:
import request from 'supertest';
import app from '../app';
describe('GET /posts', () => {
it('returns posts array', async () => {
const res = await request(app.callback())
.get('/posts')
.expect(200);
expect(res.body).to.be.an('array');
});
});
Testing Error Handling
Testing thrown errors:
function divide(a: number, b: number): number {
if (b === 0) throw new Error('Cannot divide by zero');
return a / b;
}
// Jest approach
test('throws error when dividing by zero', () => {
expect(() => divide(1, 0)).toThrow('Cannot divide by zero');
});
// Mocha approach
it('throws error when dividing by zero', () => {
expect(() => divide(1, 0)).to.throw('Cannot divide by zero');
});
Test Configuration Management
Jest environment variable management:
// jest.config.js
module.exports = {
setupFiles: ['<rootDir>/tests/setupEnv.js'],
};
// tests/setupEnv.js
process.env.NODE_ENV = 'test';
process.env.API_URL = 'http://test.api';
Mocha environment configuration:
// tests/setup.ts
import dotenv from 'dotenv';
dotenv.config({ path: '.env.test' });
// .mocharc.json
{
"require": ["ts-node/register", "./tests/setup.ts"]
}
Testing Database Operations
Using in-memory databases for testing:
import { User } from '../models';
import { initTestDB, closeTestDB } from '../test-utils';
describe('User model', () => {
beforeAll(async () => {
await initTestDB();
});
afterAll(async () => {
await closeTestDB();
});
it('creates a user', async () => {
const user = await User.create({ name: 'Test' });
expect(user.id).toBeDefined();
});
});
Testing Time-Related Code
Jest mocking timers:
jest.useFakeTimers();
test('calls callback after delay', () => {
const callback = jest.fn();
delayedCallback(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
Mocha using sinon to mock time:
import sinon from 'sinon';
describe('timed operations', () => {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
it('calls callback after delay', () => {
const callback = sinon.spy();
delayedCallback(callback, 1000);
clock.tick(1000);
expect(callback.calledOnce).to.be.true;
});
});
Testing File System Operations
Jest mocking file system:
jest.mock('fs');
import fs from 'fs';
import { readConfig } from '../config';
(fs.readFileSync as jest.Mock).mockReturnValue('{"env":"test"}');
test('reads config file', () => {
const config = readConfig();
expect(config).toEqual({ env: 'test' });
});
Mocha using mock-fs:
import mock from 'mock-fs';
import { readConfig } from '../config';
describe('config reader', () => {
beforeEach(() => {
mock({
'config.json': '{"env":"test"}'
});
});
afterEach(() => {
mock.restore();
});
it('reads config file', () => {
const config = readConfig();
expect(config).to.deep.equal({ env: 'test' });
});
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与数据库ORM