Cypress 进阶指南:精通 intercept() 方法实现网络请求拦截与模拟

在构建现代前端应用时,我们经常面临这样一个挑战:如何在不依赖不稳定后端服务的情况下,对复杂的网络交互进行测试?或者,当后端 API 尚未开发完成时,前端测试又该如何推进?这正是我们需要深入探讨 Cypress 中 intercept() 方法的原因。

作为 Cypress 测试体系中的核心功能,INLINECODE9bf7f637(曾用名 INLINECODE553e00cc)赋予了我们“上帝视角”般的控制力。它不仅能让我们监听应用发出的所有网络请求,还能让我们修改请求头、模拟各种服务器响应(包括成功、失败、延迟等),甚至动态篡改返回的数据。这意味着,我们可以彻底摆脱后端环境的束缚,编写出更加健壮、独立且速度极快的端到端测试。

在接下来的内容中,我们将不仅学习 intercept() 的基础语法,还会通过实战案例,深入探讨如何利用它来应对复杂的测试场景,比如处理跨域问题、模拟慢速网络以及验证错误处理逻辑。让我们开始这段探索之旅吧。

intercept() 方法核心解析

要掌握 intercept(),首先需要理解它的基本工作原理。简单来说,它就像是浏览器和服务器之间的一个“中间人”。当你在测试中定义了一个拦截规则后,Cypress 会监听所有匹配该规则的流量。你可以选择让流量正常通过(仅监听),也可以选择切断流量并返回你自定义的假数据(Stubbing)。

#### 语法与参数详解

intercept() 方法的调用方式非常灵活,但最常用的模式如下:

cy.intercept(method, url, response?)

让我们拆解一下这些参数:

  • method (可选): 指定要拦截的 HTTP 动词,如 INLINECODE694d23c0、INLINECODE11275ea8、INLINECODE5e8b784d、INLINECODE386e0f1f 或 INLINECODE464bb805。如果你省略此参数(或者使用通配符 INLINECODE36e264d3),Cypress 将拦截匹配 URL 的所有类型的请求。

n* url (必填): 这是请求的匹配标识符。它可以是:

n * 一个完整的 URL 字符串(例如 ‘/api/users‘)。

n * 一个正则表达式(例如 /\/api\/users\/\d+/,用于匹配特定 ID 的用户请求)。

n * 一个 Glob 模式(例如 **/users/**)。

  • response (可选): 这是最强大的部分。你可以在这里定义一个静态对象、一个包含 INLINECODEae5d5276 和 INLINECODEa0812373 的完整响应配置,甚至是一个函数,该函数接收原始请求并动态返回响应。如果省略此参数,Cypress 仅会监听请求,不会对其进行任何修改(Pass-through 模式)。

n#### 返回值与别名

n需要注意的是,intercept() 的生效时机是“未来的”。当你调用它时,它只是设置了一个规则。为了在测试后续步骤中引用这个被拦截的请求,我们通常会给它起一个别名

n

// 使用 .as() 给拦截规则起别名
cy.intercept(‘GET‘, ‘/api/data‘).as(‘getData‘)

这样,我们稍后就可以使用 cy.wait(‘@getData‘) 来等待这个请求发生,并获取该请求的对象进行断言。

场景一:基础 GET 请求模拟与断言

让我们从一个最常见的场景开始:假设我们的页面加载时会调用 API 获取用户列表。为了保证测试的稳定性,我们不希望依赖真实的后端数据库数据。我们可以拦截这个请求并返回固定的模拟数据。

准备工作:HTML 文件

请将以下代码保存为 index.html,并在本地(端口 3000)运行。这个页面模拟了一个简单的用户列表加载功能。




    
    用户列表示例
    
        body { font-family: sans-serif; padding: 20px; }
        .user-card { border: 1px solid #ccc; margin: 10px 0; padding: 10px; border-radius: 4px; }
        .error { color: red; }
    


    

当前用户:

加载中...
// 页面加载后自动发起请求 fetch(‘/api/users‘) .then(response => { if (!response.ok) throw new Error(‘网络响应异常‘); return response.json(); }) .then(data => { const list = document.getElementById(‘user-list‘); list.innerHTML = ‘‘; // 清空加载文字 // 渲染用户列表 data.forEach(user => { const div = document.createElement(‘div‘); div.className = ‘user-card‘; div.textContent = `${user.name} (ID: ${user.id})`; list.appendChild(div); }); }) .catch(error => { document.getElementById(‘user-list‘).innerHTML = `
加载失败: ${error.message}
`; });

Cypress 测试代码

在 Cypress 的 INLINECODEf4c73a1f 文件夹中创建 INLINECODE46f13a67。

describe(‘Cypress Intercept 进阶实战: GET 请求‘, () => {

    // 每次测试前访问页面
    beforeEach(() => {
        cy.visit(‘http://localhost:3000‘);
    });

    it(‘应该成功拦截 GET 请求并显示模拟的用户列表‘, () => {
        // 定义模拟数据
        const mockUsers = [
            { id: 1, name: ‘Alice‘ },
            { id: 2, name: ‘Bob‘ },
            { id: 3, name: ‘Charlie‘ }
        ];

        // 核心步骤:拦截请求并返回模拟数据
        // 我们不关心真实的 /api/users 返回什么,我们强制返回 mockUsers
        cy.intercept(‘GET‘, ‘/api/users‘, {
            statusCode: 200,
            body: mockUsers
        }).as(‘getUsers‘);

        // 等待别名对应的请求完成
        // 如果不使用 cy.wait,由于请求是异步的,断言可能会在数据到达前执行导致失败
        cy.wait(‘@getUsers‘);

        // 断言 UI 是否正确渲染了模拟数据
        cy.get(‘#user-list‘).should(‘not.contain‘, ‘加载中‘);
        cy.get(‘.user-card‘).should(‘have.length‘, 3);
        cy.get(‘.user-card‘).eq(0).should(‘contain.text‘, ‘Alice‘);
    });

    it(‘应该验证请求是否携带了正确的 Headers‘, () => {
        cy.intercept(‘GET‘, ‘/api/users‘).as(‘getUsers‘);

        cy.wait(‘@getUsers‘).then((interception) => {
            // 在这里我们可以深入检查请求的细节
            expect(interception.request.headers).to.have.property(‘accept‘);
            // 甚至可以断言请求 URL
            expect(interception.request.url).to.include(‘/api/users‘);
        });
    });
});

场景二:模拟 POST 提交与动态响应

GET 请求通常是静态的,但在处理 POST 请求时(如表单提交),我们经常需要根据用户发送的内容来动态决定返回什么。INLINECODE8e537739 允许我们传入一个函数作为 INLINECODE204acd04 参数,从而实现动态逻辑。

HTML 文件 (index_post.html)




    
    登录表单


    

用户登录

document.getElementById(‘loginForm‘).addEventListener(‘submit‘, (e) => { e.preventDefault(); const user = document.getElementById(‘username‘).value; const pass = document.getElementById(‘password‘).value; fetch(‘/api/login‘, { method: ‘POST‘, body: JSON.stringify({ username: user, password: pass }) }) .then(resp => resp.json()) .then(data => { document.getElementById(‘msg‘).innerText = data.message; // 模拟登录成功后的跳转或状态变化 if(data.success) { document.getElementById(‘msg‘).style.color = ‘green‘; } else { document.getElementById(‘msg‘).style.color = ‘red‘; } }); });

Cypress 测试代码

describe(‘Cypress Intercept 进阶实战: 动态 POST 请求‘, () => {

    beforeEach(() => {
        cy.visit(‘http://localhost:3000/index_post.html‘);
    });

    it(‘应根据输入动态验证登录逻辑‘, () => {
        // 使用函数来实现动态响应
        cy.intercept(‘POST‘, ‘/api/login‘, (req) => {
            // req.body 包含前端发送的数据
            const { username, password } = req.body;

            // 模拟后端逻辑:用户名为 ‘admin‘ 且密码为 ‘123456‘ 时成功
            if (username === ‘admin‘ && password === ‘123456‘) {
                req.reply({
                    statusCode: 200,
                    body: { success: true, message: ‘欢迎回来,管理员!‘ }
                });
            } else {
                // 模拟密码错误
                req.reply({
                    statusCode: 401, // Unauthorized
                    body: { success: false, message: ‘用户名或密码错误‘ }
                });
            }
        }).as(‘loginRequest‘);

        // 测试失败场景
        cy.get(‘#username‘).type(‘admin‘);
        cy.get(‘#password‘).type(‘wrong_pass‘);
        cy.get(‘button[type="submit"]‘).click();

        cy.wait(‘@loginRequest‘);
        cy.get(‘#msg‘).should(‘contain.text‘, ‘用户名或密码错误‘).and(‘have.css‘, ‘color‘, ‘rgb(255, 0, 0)‘);

        // 测试成功场景
        cy.get(‘#username‘).clear().type(‘admin‘);
        cy.get(‘#password‘).clear().type(‘123456‘);
        cy.get(‘button[type="submit"]‘).click();

        cy.wait(‘@loginRequest‘);
        cy.get(‘#msg‘).should(‘contain.text‘, ‘欢迎回来‘).and(‘have.css‘, ‘color‘, ‘rgb(0, 128, 0)‘);
    });
});

场景三:模拟网络故障与边界情况

作为测试人员,我们不能只测试“阳光明媚”的日子。网络超时、服务器 500 错误、跨域 CORS 问题在真实环境中时有发生。intercept 让我们能够轻松复现这些噩梦般的场景。

示例:模拟 500 内部服务器错误

describe(‘错误处理测试‘, () => {
    it(‘应该优雅地处理服务器 500 错误‘, () => {
        // 模拟服务器崩溃
        cy.intercept(‘GET‘, ‘/api/critical-data‘, {
            statusCode: 500,
            body: { error: ‘Internal Server Error‘ }
        }).as(‘serverError‘);

        cy.visit(‘/dashboard‘);
        
        // 等待请求完成
        cy.wait(‘@serverError‘);

        // 假设页面有一个错误提示区域
        cy.get(‘.error-boundary‘).should(‘be.visible‘)
            .and(‘contain.text‘, ‘服务暂时不可用,请稍后再试‘);
    });
});

示例:模拟网络延迟

有时候,UI 会出现 Loading 状态,但我们可能因为开发环境网络太快而无法测试这个状态。我们可以强制增加延迟。

describe(‘网络延迟测试‘, () => {
    it(‘应该显示 Loading 指示器直到请求完成‘, () => {
        // 设置 3 秒的延迟
        cy.intercept(‘GET‘, ‘/api/profile‘, {
            delay: 3000, // 关键点:delay 毫秒
            statusCode: 200,
            body: { name: ‘Slowpoke‘ }
        }).as(‘slowRequest‘);

        cy.visit(‘/profile‘);

        // 断言 Loading 元素立即出现
        cy.get(‘.loading-spinner‘).should(‘be.visible‘);

        // 等待慢速请求
        cy.wait(‘@slowRequest‘);

        // 断言 Loading 元素消失
        cy.get(‘.loading-spinner‘).should(‘not.exist‘);
    });
});

实战中的常见陷阱与最佳实践

在实际的大型项目中使用 intercept 时,你可能会遇到一些“坑”。让我们来看看如何避免它们。

#### 1. 请求竞态条件

这是新手最容易遇到的问题。假设你的测试代码是这样的:

// 错误示范
cy.visit(‘/app‘);
cy.get(‘button‘).click(); // 点击按钮触发 API 请求
cy.intercept(‘POST‘, ‘/api/save‘, { success: true }).as(‘save‘); // 在点击之后才拦截?
cy.wait(‘@save‘);

问题: INLINECODEaefa9bd1 并不会回溯过去已经发生的事件。如果 INLINECODE6addea8f 或 INLINECODE323c6dfe 触发的请求在你调用 INLINECODE7fa87173 之前就已经发出去了,拦截将失败。
解决方案: 总是设置拦截,执行触发请求的操作(如 INLINECODE80b99797 或 INLINECODE2c9f5814)。这就是为什么我们通常将 INLINECODE55b0edc7 放在 INLINECODEdb545542 或者测试用例的最顶部。

// 正确示范
cy.intercept(‘POST‘, ‘/api/save‘, { success: true }).as(‘save‘); // 先布网
cy.visit(‘/app‘);
cy.get(‘button‘).click(); // 再触网
cy.wait(‘@save‘); // 最后守株待兔

#### 2. Fixture 文件的使用

将大量的 JSON 数据硬编码在测试文件中会让代码变得非常杂乱。我们可以使用 INLINECODE7189c61e 来加载外部文件,或者直接在 INLINECODE23995e1e 中引用 Fixture。

it(‘应该使用 fixture 文件作为响应体‘, () => {
    // 方式一:直接使用字符串引用
    cy.intercept(‘GET‘, ‘/api/products‘, { fixture: ‘products.json‘ }).as(‘getProducts‘);

    // 方式二:加载并动态修改
    cy.fixture(‘products.json‘).then((data) => {
        // 假设我们想修改第一条数据
        data[0].price = 999;

        cy.intercept(‘GET‘, ‘/api/products‘, { body: data }).as(‘getProductsModified‘);
    });
});

#### 3. 跨域 (CORS) 问题

有时候你的前端应用运行在 INLINECODEf94f55b6,而 API 在 INLINECODEee6fd9ba。如果浏览器因为 CORS 策略阻止了请求,Cypress 的 intercept 也可能捕获不到响应。

解决思路: 虽然现代浏览器出于安全考虑限制 CORS,但在 Cypress 测试环境中,你可以通过配置 INLINECODE6c26e511(在 INLINECODE81340dc4 中)来绕过这些限制,但这通常只是最后的手段。更优雅的方式是确保你的开发环境配置了代理,将 API 请求代理到同源地址下,这样 cy.intercept 就能无缝工作。

总结

掌握 cy.intercept() 标志着我们从“点点点”的手动测试思维,转向了自动化的、代码驱动的验证思维。它不仅仅是一个用来 Mock 数据的工具,更是我们理解和控制应用网络行为的强大武器。

回顾一下,我们今天学习了:

  • 如何使用 intercept 拦截并修改 GET 和 POST 请求。
  • 如何利用别名 (INLINECODE387378eb) 和 INLINECODEc262ef1c 来同步异步操作,断言 UI 状态。
  • 如何通过动态函数响应和 delay 参数模拟复杂的网络状况。
  • 避免竞态条件等常见错误的最佳实践。

在你的下一个项目中,试着不再依赖不稳定的测试服务器,而是利用 intercept 构建一套完全独立、运行迅速且覆盖全面的测试套件吧。你会发现,这种对代码掌控力的提升,会让你的开发流程变得更加自信和高效。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/50408.html
点赞
0.00 平均评分 (0% 分数) - 0