Cypress 网络请求模拟 #
理解网络控制 #
为什么需要网络控制? #
text
┌─────────────────────────────────────────────────────────────┐
│ 传统测试的痛点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ 依赖真实 API 服务器 │
│ ❌ 测试结果不稳定(网络问题) │
│ ❌ 无法测试边界情况(错误、超时) │
│ ❌ 测试速度慢 │
│ ❌ 无法隔离测试 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Cypress 网络控制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 拦截和修改请求 │
│ ✅ 模拟任意响应 │
│ ✅ 测试错误场景 │
│ ✅ 快速稳定的测试 │
│ ✅ 完全控制网络层 │
│ │
└─────────────────────────────────────────────────────────────┘
cy.intercept 基础 #
基本用法 #
javascript
// 拦截 GET 请求
cy.intercept('GET', '/api/users').as('getUsers');
// 拦截 POST 请求
cy.intercept('POST', '/api/users').as('createUser');
// 拦截所有请求
cy.intercept('/api/**').as('apiCalls');
等待请求 #
javascript
cy.intercept('GET', '/api/users').as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
// 验证请求
cy.wait('@getUsers').then((interception) => {
expect(interception.request.method).to.eq('GET');
expect(interception.response.statusCode).to.eq(200);
});
模拟响应 #
静态响应 #
javascript
// 返回静态数据
cy.intercept('GET', '/api/users', [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
// 返回对象
cy.intercept('GET', '/api/users/1', {
id: 1,
name: 'John',
email: 'john@example.com'
});
使用 fixture #
javascript
// 使用 fixture 文件
cy.intercept('GET', '/api/users', { fixture: 'users.json' });
// fixture 文件路径:cypress/fixtures/users.json
动态响应 #
javascript
// 使用函数动态生成响应
cy.intercept('GET', '/api/users', (req) => {
req.reply({
statusCode: 200,
body: {
users: [
{ id: 1, name: 'User ' + Date.now() }
]
}
});
});
响应配置 #
javascript
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: { users: [] },
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
delay: 1000 // 延迟 1 秒
});
修改请求 #
修改请求体 #
javascript
cy.intercept('POST', '/api/users', (req) => {
// 修改请求体
req.body.createdAt = new Date().toISOString();
// 继续发送请求
req.continue();
});
修改请求头 #
javascript
cy.intercept('**', (req) => {
req.headers['Authorization'] = 'Bearer test-token';
req.continue();
});
修改 URL #
javascript
cy.intercept('GET', '/api/users', (req) => {
// 重定向到其他 URL
req.url = '/api/v2/users';
req.continue();
});
修改查询参数 #
javascript
cy.intercept('GET', '/api/users', (req) => {
req.query.limit = '100';
req.continue();
});
修改响应 #
修改响应体 #
javascript
cy.intercept('GET', '/api/users', (req) => {
req.continue((res) => {
// 修改响应体
res.body.users.forEach(user => {
user.processed = true;
});
});
});
修改响应状态码 #
javascript
cy.intercept('GET', '/api/users', (req) => {
req.continue((res) => {
res.statusCode = 201;
});
});
修改响应头 #
javascript
cy.intercept('GET', '/api/users', (req) => {
req.continue((res) => {
res.headers['X-Custom-Header'] = 'modified';
});
});
模拟错误场景 #
网络错误 #
javascript
// 模拟网络错误
cy.intercept('GET', '/api/users', {
forceNetworkError: true
});
服务器错误 #
javascript
// 500 错误
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: {
error: 'Internal Server Error'
}
});
// 404 错误
cy.intercept('GET', '/api/users/999', {
statusCode: 404,
body: {
error: 'User not found'
}
});
// 401 未授权
cy.intercept('GET', '/api/protected', {
statusCode: 401,
body: {
error: 'Unauthorized'
}
});
超时 #
javascript
// 模拟超时
cy.intercept('GET', '/api/users', (req) => {
req.reply({
delay: 30000, // 30 秒延迟
body: {}
});
});
延迟响应 #
javascript
// 延迟响应
cy.intercept('GET', '/api/users', {
delay: 2000, // 延迟 2 秒
body: { users: [] }
});
// 随机延迟
cy.intercept('GET', '/api/users', (req) => {
const delay = Math.random() * 3000;
req.reply({
delay,
body: { users: [] }
});
});
请求匹配 #
URL 匹配 #
javascript
// 精确匹配
cy.intercept('GET', '/api/users');
// 通配符匹配
cy.intercept('GET', '/api/*');
cy.intercept('GET', '/api/**');
// 正则表达式
cy.intercept('GET', /\/api\/users\/\d+/);
// 对象匹配
cy.intercept({
method: 'GET',
url: '/api/users',
query: { page: '1' }
});
方法匹配 #
javascript
// 匹配特定方法
cy.intercept('GET', '/api/users');
cy.intercept('POST', '/api/users');
cy.intercept('PUT', '/api/users/*');
cy.intercept('DELETE', '/api/users/*');
// 匹配所有方法
cy.intercept('/api/users');
请求体匹配 #
javascript
// 匹配请求体内容
cy.intercept({
method: 'POST',
url: '/api/users',
body: {
name: 'John'
}
}, { statusCode: 201 });
请求头匹配 #
javascript
// 匹配请求头
cy.intercept({
url: '/api/users',
headers: {
'Authorization': 'Bearer token123'
}
});
高级用法 #
条件响应 #
javascript
cy.intercept('GET', '/api/users', (req) => {
const page = req.query.page;
if (page === '1') {
req.reply({ fixture: 'users-page1.json' });
} else {
req.reply({ fixture: 'users-page2.json' });
}
});
请求计数 #
javascript
let requestCount = 0;
cy.intercept('GET', '/api/users', (req) => {
requestCount++;
req.reply({ body: { count: requestCount } });
});
动态路由 #
javascript
cy.intercept('GET', /\/api\/users\/(\d+)/, (req) => {
const match = req.url.match(/\/api\/users\/(\d+)/);
const userId = match[1];
req.reply({
body: {
id: parseInt(userId),
name: `User ${userId}`
}
});
});
链式拦截 #
javascript
// 多个拦截器按顺序执行
cy.intercept('/api/**', (req) => {
console.log('全局拦截');
req.continue();
});
cy.intercept('/api/users', (req) => {
console.log('用户 API 拦截');
req.reply({ body: [] });
});
cy.request #
发送请求 #
javascript
// GET 请求
cy.request('GET', '/api/users');
// POST 请求
cy.request('POST', '/api/users', {
name: 'John',
email: 'john@example.com'
});
// PUT 请求
cy.request('PUT', '/api/users/1', { name: 'Jane' });
// DELETE 请求
cy.request('DELETE', '/api/users/1');
请求选项 #
javascript
cy.request({
method: 'POST',
url: '/api/users',
body: { name: 'John' },
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json'
},
timeout: 10000,
failOnStatusCode: false // 不因 4xx/5xx 失败
});
处理响应 #
javascript
cy.request('GET', '/api/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
expect(response.body).to.have.length(5);
expect(response.headers).to.have.property('content-type');
});
// 解构响应
cy.request('GET', '/api/users/1').its('body.name').should('eq', 'John');
认证请求 #
javascript
// Basic 认证
cy.request({
url: '/api/protected',
auth: {
user: 'username',
pass: 'password'
}
});
// Bearer Token
cy.request({
url: '/api/protected',
headers: {
'Authorization': 'Bearer token123'
}
});
测试 API #
API 测试示例 #
javascript
describe('用户 API 测试', () => {
const baseUrl = '/api/users';
it('获取用户列表', () => {
cy.request('GET', baseUrl).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
expect(response.body.length).to.be.greaterThan(0);
});
});
it('创建用户', () => {
cy.request('POST', baseUrl, {
name: 'Test User',
email: 'test@example.com'
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('id');
expect(response.body.name).to.eq('Test User');
});
});
it('更新用户', () => {
cy.request('PUT', `${baseUrl}/1`, {
name: 'Updated Name'
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body.name).to.eq('Updated Name');
});
});
it('删除用户', () => {
cy.request('DELETE', `${baseUrl}/1`).then((response) => {
expect(response.status).to.eq(204);
});
});
it('处理错误', () => {
cy.request({
url: `${baseUrl}/999`,
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404);
});
});
});
网络最佳实践 #
1. 使用别名管理请求 #
javascript
// ✅ 好的做法
cy.intercept('GET', '/api/users').as('getUsers');
cy.intercept('POST', '/api/users').as('createUser');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('.create-button').click();
cy.wait('@createUser');
2. 测试前后清理数据 #
javascript
beforeEach(() => {
// 清理测试数据
cy.request('POST', '/api/test/reset');
});
afterEach(() => {
// 清理创建的数据
cy.request('DELETE', '/api/test/cleanup');
});
3. 模拟真实场景 #
javascript
// 模拟真实的网络延迟
cy.intercept('GET', '/api/users', {
delay: 100,
body: { users: [] }
});
// 模拟网络不稳定
cy.intercept('GET', '/api/users', (req) => {
if (Math.random() > 0.8) {
req.reply({ forceNetworkError: true });
} else {
req.reply({ body: { users: [] } });
}
});
4. 验证请求参数 #
javascript
cy.intercept('POST', '/api/users').as('createUser');
cy.get('.submit-button').click();
cy.wait('@createUser').its('request.body').should('deep.equal', {
name: 'John',
email: 'john@example.com'
});
完整示例 #
javascript
describe('网络请求模拟示例', () => {
beforeEach(() => {
// 设置基础拦截
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.intercept('POST', '/api/users').as('createUser');
cy.intercept('PUT', '/api/users/*').as('updateUser');
cy.intercept('DELETE', '/api/users/*').as('deleteUser');
});
it('显示用户列表', () => {
cy.visit('/users');
cy.wait('@getUsers');
cy.get('.user-item').should('have.length', 5);
});
it('创建新用户', () => {
cy.intercept('POST', '/api/users', {
statusCode: 201,
body: { id: 6, name: 'New User' }
}).as('createUser');
cy.visit('/users');
cy.get('.add-user').click();
cy.get('#name').type('New User');
cy.get('.submit').click();
cy.wait('@createUser').then((interception) => {
expect(interception.request.body.name).to.eq('New User');
});
cy.get('.success-message').should('be.visible');
});
it('处理加载状态', () => {
cy.intercept('GET', '/api/users', {
delay: 2000,
fixture: 'users.json'
}).as('getUsers');
cy.visit('/users');
// 验证加载状态
cy.get('.loading').should('be.visible');
cy.wait('@getUsers');
// 验证加载完成
cy.get('.loading').should('not.exist');
cy.get('.user-item').should('exist');
});
it('处理错误场景', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Server Error' }
}).as('getUsersError');
cy.visit('/users');
cy.wait('@getUsersError');
cy.get('.error-message').should('contain', '加载失败');
cy.get('.retry-button').should('be.visible');
});
it('修改请求和响应', () => {
cy.intercept('GET', '/api/users', (req) => {
// 添加请求头
req.headers['X-Test'] = 'true';
req.continue((res) => {
// 修改响应
res.body.users.forEach(user => {
user.tested = true;
});
});
}).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('.user-item').first().should('contain', 'tested');
});
it('API 直接测试', () => {
cy.request('GET', '/api/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
});
cy.request('POST', '/api/users', {
name: 'API Test User'
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body.name).to.eq('API Test User');
});
});
});
下一步 #
现在你已经掌握了 Cypress 网络请求模拟的方法,接下来学习 测试数据管理 了解如何管理测试数据!
最后更新:2026-03-28