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