- name
- framework-testing-javascript
- description
- Acts as a Specialist in Automated Testing and QA Engineering in the JavaScript and TypeScript ecosystem (Node.js, React, Vue, NestJS). Covers the all-in-one Jest framework and the modular Mocha architecture with Chai and Sinon.js, mastering Spies, Stubs, Mocks, BDD/TDD assertions, Snapshots, Fake Timers, asynchronous testing, and code coverage with Istanbul/c8.
# Automated Testing in JavaScript & TypeScript: Jest & Mocha
For interface tests, use [rendered interface oracles](../framework-testing/references/rendered-ui-oracles.md). Wait for the selected state and relevant resources, then assert visible output and ownership, not only classes or event calls. Exercise out-of-order completions and verify a valid update still succeeds; fake clocks and source snapshots alone do not establish rendered behavior.
This skill establishes the engineering guidelines and standards for developing and automating tests in the JavaScript and TypeScript ecosystem, spanning batteries-included solutions such as **Jest** and modular compositions with **Mocha + Chai + Sinon.js**.
---
## 🧭 1. Framework Comparison and Selection Guidelines
| Characteristic | Jest | Mocha + Chai + Sinon.js |
| :--- | :--- | :--- |
| **Architecture** | "All-in-one" framework (Runner + Assertions + Mocks) | Decoupled runner with a plugin ecosystem |
| **Mocks & Spies** | Native (`jest.fn()`, `jest.spyOn()`, `jest.mock()`) | Via Sinon.js (`sinon.stub()`, `sinon.spy()`) |
| **Assertions** | Rich native matchers (`expect(val).toBe()`) | Chai library (`expect`, `should`, `assert`) |
| **Snapshot Testing** | Native support (`toMatchSnapshot()`) | Requires additional plugins |
| **Typical Environment** | React, Vue, NestJS, TypeScript, Next.js | Legacy Node.js microservices, Express, pure ESM |
---
## ⚡ 2. Jest: Native Test and Mock Patterns
### 2.1 Unit Tests and Native Matchers
```typescript
import { calculateOrderTotal } from './calculator';
describe('calculateOrderTotal', () => {
it('should calculate the total with discount and shipping correctly', () => {
const items = [{ price: 100, quantity: 2 }, { price: 50, quantity: 1 }];
const discount = 20;
const shipping = 15;
const result = calculateOrderTotal(items, discount, shipping);
expect(result).toBe(245); // (200 + 50) - 20 + 15
});
it('should throw an error for negative quantity', () => {
const invalidItems = [{ price: 100, quantity: -1 }];
expect(() => calculateOrderTotal(invalidItems, 0, 0)).toThrow('Invalid quantity');
});
});
```
### 2.2 Module and Asynchronous Function Mocks
```typescript
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
jest.mock('./user.repository');
describe('UserService', () => {
let userService: UserService;
let mockRepo: jest.Mocked<UserRepository>;
beforeEach(() => {
jest.clearAllMocks();
mockRepo = new UserRepository() as jest.Mocked<UserRepository>;
userService = new UserService(mockRepo);
});
it('should return the user when found in the repository', async () => {
mockRepo.findById.mockResolvedValue({ id: 'u1', name: 'Alice' });
const user = await userService.getUser('u1');
expect(mockRepo.findById).toHaveBeenCalledWith('u1');
expect(user.name).toBe('Alice');
});
});
```
---
## ☕ 3. Mocha + Chai + Sinon.js: Modular Architecture
### 3.1 BDD Testing with Chai and a Sinon.js Sandbox
```javascript
const { expect } = require('chai');
const sinon = require('sinon');
const { PaymentService } = require('../src/payment.service');
const { PaymentGateway } = require('../src/payment.gateway');
describe('PaymentService (Mocha + Chai + Sinon)', () => {
let sandbox;
let paymentService;
let gatewayMock;
beforeEach(() => {
sandbox = sinon.createSandbox();
gatewayMock = new PaymentGateway();
paymentService = new PaymentService(gatewayMock);
});
afterEach(() => {
sandbox.restore(); // Restores all stubs and spies
});
it('should process the payment and record the transaction', async () => {
const stub = sandbox.stub(gatewayMock, 'charge').resolves({ status: 'SUCCESS', id: 'tx-999' });
const result = await paymentService.processPayment({ amount: 100 });
expect(stub.calledOnce).to.be.true;
expect(result.status).to.equal('SUCCESS');
expect(result.id).to.equal('tx-999');
});
});
```
---
## 🧪 4. Fake Timers and Asynchronous Tests
### 4.1 Jest Fake Timers
```typescript
jest.useFakeTimers();
it('should execute the callback after a 300ms debounce', () => {
const callback = jest.fn();
const debounced = debounce(callback, 300);
debounced();
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(callback).toHaveBeenCalledTimes(1);
});
```
在 GitHub 查看