测试 #
一、测试概述 #
1.1 测试类型 #
| 类型 | 说明 |
|---|---|
| 单元测试 | 测试单个函数或模块 |
| 集成测试 | 测试多个组件协作 |
| 端到端测试 | 测试完整用户流程 |
1.2 测试工具 #
bash
npm install --save-dev jest
二、项目配置 #
2.1 package.json #
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
2.2 jest.config.js #
javascript
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.js'],
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov']
};
三、服务器测试 #
3.1 创建测试服务器 #
javascript
const Hapi = require('@hapi/hapi');
const createServer = async () => {
const server = Hapi.server({
host: 'localhost',
port: 3000
});
server.route([
{
method: 'GET',
path: '/',
handler: (request, h) => {
return { message: 'Hello World' };
}
},
{
method: 'GET',
path: '/users/{id}',
handler: (request, h) => {
return { id: request.params.id };
}
}
]);
await server.initialize();
return server;
};
module.exports = createServer;
3.2 测试服务器 #
javascript
const createServer = require('./server');
describe('Server', () => {
let server;
beforeEach(async () => {
server = await createServer();
});
afterEach(async () => {
await server.stop();
});
test('should start server', () => {
expect(server).toBeDefined();
expect(server.info.port).toBe(3000);
});
test('should return hello world', async () => {
const response = await server.inject({
method: 'GET',
path: '/'
});
expect(response.statusCode).toBe(200);
expect(response.result).toEqual({ message: 'Hello World' });
});
});
四、路由测试 #
4.1 GET请求测试 #
javascript
describe('GET /users', () => {
let server;
beforeEach(async () => {
server = await createServer();
});
afterEach(async () => {
await server.stop();
});
test('should return all users', async () => {
const response = await server.inject({
method: 'GET',
path: '/users'
});
expect(response.statusCode).toBe(200);
expect(Array.isArray(response.result)).toBe(true);
});
test('should return user by id', async () => {
const response = await server.inject({
method: 'GET',
path: '/users/1'
});
expect(response.statusCode).toBe(200);
expect(response.result.id).toBe('1');
});
test('should return 404 for non-existent user', async () => {
const response = await server.inject({
method: 'GET',
path: '/users/999'
});
expect(response.statusCode).toBe(404);
});
});
4.2 POST请求测试 #
javascript
describe('POST /users', () => {
let server;
beforeEach(async () => {
server = await createServer();
});
afterEach(async () => {
await server.stop();
});
test('should create user', async () => {
const response = await server.inject({
method: 'POST',
path: '/users',
payload: {
name: 'John Doe',
email: 'john@example.com'
}
});
expect(response.statusCode).toBe(201);
expect(response.result.name).toBe('John Doe');
});
test('should reject invalid data', async () => {
const response = await server.inject({
method: 'POST',
path: '/users',
payload: {
name: 'J'
}
});
expect(response.statusCode).toBe(400);
});
});
4.3 认证路由测试 #
javascript
describe('GET /profile', () => {
let server;
beforeEach(async () => {
server = await createServer();
});
afterEach(async () => {
await server.stop();
});
test('should return 401 without token', async () => {
const response = await server.inject({
method: 'GET',
path: '/profile'
});
expect(response.statusCode).toBe(401);
});
test('should return profile with valid token', async () => {
const token = generateTestToken();
const response = await server.inject({
method: 'GET',
path: '/profile',
headers: {
Authorization: `Bearer ${token}`
}
});
expect(response.statusCode).toBe(200);
expect(response.result).toHaveProperty('id');
});
});
五、处理函数测试 #
5.1 单元测试 #
javascript
const handlers = require('./handlers/users');
describe('User Handlers', () => {
describe('getAll', () => {
test('should return all users', async () => {
const mockRequest = {
query: { page: 1, limit: 10 }
};
const mockH = {
response: jest.fn().mockReturnThis()
};
const result = await handlers.getAll(mockRequest, mockH);
expect(result).toBeDefined();
});
});
describe('getById', () => {
test('should return user by id', async () => {
const mockRequest = {
params: { id: '1' }
};
const mockH = {
response: jest.fn().mockReturnThis()
};
const result = await handlers.getById(mockRequest, mockH);
expect(result).toBeDefined();
});
});
});
六、插件测试 #
6.1 测试插件 #
javascript
const myPlugin = require('./plugins/my-plugin');
describe('My Plugin', () => {
let server;
beforeEach(async () => {
server = Hapi.server({ port: 3000 });
await server.register(myPlugin);
});
afterEach(async () => {
await server.stop();
});
test('should register successfully', () => {
expect(server.registrations['my-plugin']).toBeDefined();
});
test('should add routes', async () => {
const response = await server.inject({
method: 'GET',
path: '/plugin-route'
});
expect(response.statusCode).toBe(200);
});
test('should add methods', () => {
expect(server.methods.myMethod).toBeDefined();
});
});
七、验证测试 #
7.1 测试验证 #
javascript
const Joi = require('@hapi/joi');
describe('Validation', () => {
const userSchema = Joi.object({
name: Joi.string().min(2).required(),
email: Joi.string().email().required()
});
test('should pass valid data', () => {
const { error } = userSchema.validate({
name: 'John',
email: 'john@example.com'
});
expect(error).toBeUndefined();
});
test('should fail invalid email', () => {
const { error } = userSchema.validate({
name: 'John',
email: 'invalid-email'
});
expect(error).toBeDefined();
expect(error.details[0].type).toBe('string.email');
});
test('should fail missing name', () => {
const { error } = userSchema.validate({
email: 'john@example.com'
});
expect(error).toBeDefined();
expect(error.details[0].type).toBe('any.required');
});
});
八、Mock测试 #
8.1 Mock服务 #
javascript
jest.mock('./services/userService');
const UserService = require('./services/userService');
const handlers = require('./handlers/users');
describe('User Handlers with Mock', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should call UserService.findAll', async () => {
UserService.findAll.mockResolvedValue([
{ id: 1, name: 'John' }
]);
const mockRequest = {
query: { page: 1, limit: 10 }
};
const result = await handlers.getAll(mockRequest, {});
expect(UserService.findAll).toHaveBeenCalledWith({ page: 1, limit: 10 });
expect(result).toHaveLength(1);
});
});
8.2 Mock数据库 #
javascript
const mockDb = {
query: jest.fn()
};
jest.mock('./db', () => mockDb);
describe('Database Operations', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should query database', async () => {
mockDb.query.mockResolvedValue({
rows: [{ id: 1, name: 'John' }]
});
const result = await getUserById(1);
expect(mockDb.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = $1',
[1]
);
expect(result).toEqual({ id: 1, name: 'John' });
});
});
九、测试覆盖率 #
9.1 运行覆盖率 #
bash
npm run test:coverage
9.2 覆盖率报告 #
javascript
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
十、完整测试示例 #
10.1 完整测试文件 #
javascript
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const createServer = async () => {
const server = Hapi.server({ port: 3000 });
const users = [];
server.route([
{
method: 'GET',
path: '/users',
options: {
validate: {
query: Joi.object({
page: Joi.number().default(1),
limit: Joi.number().default(10)
})
}
},
handler: (request, h) => {
return {
users,
page: request.query.page,
limit: request.query.limit
};
}
},
{
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).required(),
email: Joi.string().email().required()
})
}
},
handler: (request, h) => {
const user = {
id: users.length + 1,
...request.payload
};
users.push(user);
return h.response(user).code(201);
}
}
]);
await server.initialize();
return server;
};
describe('User API', () => {
let server;
beforeEach(async () => {
server = await createServer();
});
afterEach(async () => {
await server.stop();
});
describe('GET /users', () => {
test('should return empty array', async () => {
const response = await server.inject({
method: 'GET',
path: '/users'
});
expect(response.statusCode).toBe(200);
expect(response.result.users).toEqual([]);
});
test('should use default pagination', async () => {
const response = await server.inject({
method: 'GET',
path: '/users'
});
expect(response.result.page).toBe(1);
expect(response.result.limit).toBe(10);
});
});
describe('POST /users', () => {
test('should create user', async () => {
const response = await server.inject({
method: 'POST',
path: '/users',
payload: {
name: 'John Doe',
email: 'john@example.com'
}
});
expect(response.statusCode).toBe(201);
expect(response.result.id).toBe(1);
expect(response.result.name).toBe('John Doe');
});
test('should reject invalid email', async () => {
const response = await server.inject({
method: 'POST',
path: '/users',
payload: {
name: 'John',
email: 'invalid'
}
});
expect(response.statusCode).toBe(400);
});
test('should reject short name', async () => {
const response = await server.inject({
method: 'POST',
path: '/users',
payload: {
name: 'J',
email: 'john@example.com'
}
});
expect(response.statusCode).toBe(400);
});
});
});
十一、总结 #
测试要点:
| 测试类型 | 工具 |
|---|---|
| 单元测试 | Jest |
| 集成测试 | server.inject() |
| Mock | jest.mock() |
| 覆盖率 | Jest Coverage |
下一步,让我们学习实战案例!
最后更新:2026-03-28