阿里云主机折上折
  • 微信号
Current Site:Index > with testing frameworks (Jest/Mocha)

with testing frameworks (Jest/Mocha)

Author:Chuan Chen 阅读数:23093人阅读 分类: TypeScript

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.