Cypress API 测试:如何使用 cy.request() 测试 API

告别繁琐的UI操作,用Cypress直接搞定API测试!本文深入剖析cy.request()的核心用法,教你如何快速断言响应、链式调用处理Auth Token,助你实现更高效的接口与端到端测试。

用 Apifox,节省研发团队的每一分钟

Cypress API 测试:如何使用 cy.request() 测试 API

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

你已经使用 Cypress 运行端到端测试了。现在你想直接调用 API、检查状态码并对 JSON body 进行断言,而无需通过 UI 界面进行点击操作。Cypress 完全可以做到这一点。cy.request() 命令从浏览器外部的 Node 进程发送真实的 HTTP 请求,因此你可以单独测试某个接口,或者在 UI 测试运行之前进行状态初始化。

本指南将向你展示如何使用 Cypress 进行 API 测试:包括 cy.request() 的基础用法、对响应进行断言、链式调用请求以复用 auth Token,以及使用 cy.intercept() 拦截和模拟浏览器流量。你还将了解到 Cypress 在 API 测试中的适用场景,以及在什么情况下使用原生 API 工具更为合适。

为什么使用 Cypress 测试 API

大多数 Cypress 测试套件都是驱动浏览器运行的。但你在 UI 中验证的很多内容,本质上都是底层的 API:例如 /login 是否返回了 Token,/users 返回的数据结构是否正确,POST 请求是否创建了预期的记录。

直接测试这些接口有两个好处。首先,API 检查的运行速度比 UI 流程更快,因为没有页面渲染,也不需要等待元素加载。其次,你可以使用 cy.request() 来初始化测试数据。无需通过点击注册表单来创建用户,只需发送一个 POST 请求,然后直接进入你真正关心的断言步骤。

如果你的技术栈中已经包含了 Cypress,那么添加 API 测试就意味着无需安装任何新工具。你可以在同一个测试文件中编写它们,使用相同的 expect 断言,并在同一个 CI 流程中运行。

cy.request() 基础知识

cy.request() 用于发送 HTTP 请求并获取响应。它在 Cypress 的 Node 进程中运行,而不是在浏览器中运行,因此不受 CORS 或同源策略的限制。

该命令支持以下几种调用形式:

cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)

最简单的调用方式是传入一个 URL。如果没有指定方法,Cypress 默认会使用 GET

cy.request('https://jsonplaceholder.typicode.com/users')

对于除基本 GET 之外的其他操作,可以传入一个 options object。这让你能够完全控制方法、body 和 headers:

cy.request({
  method: 'POST',
  url: 'https://jsonplaceholder.typicode.com/posts',
  headers: {
    'Content-Type': 'application/json'
  },
  body: {
    title: 'API test',
    body: 'created from Cypress',
    userId: 1
  }
})

该 object 中常用的配置项包括:

  • method:HTTP 请求方法(GETPOSTPUTPATCHDELETE 等)。默认为 GET
  • url:接口地址。必填项。
  • body:请求的 payload。Cypress 会自动将对象序列化为 JSON。
  • headers:请求 header 的 object。
  • auth:用于 HTTP 基础认证(Basic Authentication)的凭证。
  • qs:以 object 形式表示的查询字符串参数(query string parameters)。
  • failOnStatusCode:当你想要对 4xx 或 5xx 响应进行断言而不是直接让测试失败时,将此项设置为 false

最后一个选项对于异常测试非常重要。默认情况下,如果收到任何非 2xx 或 3xx 的状态码,cy.request() 都会使测试失败。如果你要验证一个错误的请求是否返回 400,你需要禁用此默认行为:

cy.request({
  method: 'GET',
  url: 'https://jsonplaceholder.typicode.com/users/99999',
  failOnStatusCode: false
}).then((response) => {
  expect(response.status).to.eq(404)
})

断言 status 和 body

cy.request() 会返回一个响应对象。可以通过链式调用 .then() 来读取它。该响应包含以下四个最常用的属性:

  • status:HTTP 状态码。
  • body:响应 payload。当 Content-Typejson 结尾时,Cypress 会自动将其解析为 JavaScript 对象。
  • headers:响应 header。
  • duration:请求耗时(毫秒)。

以下是一个完整的测试,用于验证 status、body 结构以及特定字段:

describe('Users API', () => {
  it('returns a list of users', () => {
    cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
      expect(response.status).to.eq(200)
      expect(response.body).to.be.an('array')
      expect(response.body).to.have.length(10)
      expect(response.body[0]).to.have.property('email')
    })
  })
})

由于 response.body 已经是一个解析后的对象,你可以使用标准的 Chai 对其进行断言。你可以验证类型、长度、嵌套属性或具体的值。这与你用于 UI 测试的断言语法完全相同,因此无需学习新概念。

你也可以对响应 header 进行断言,这在验证内容类型或缓存设置时非常有用:

cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
  expect(response.headers['content-type']).to.include('application/json')
})

欲深入了解如何构建这些校验,请参阅我们的 API 断言指南以及更广泛的 API 测试最佳实践。

链式请求与复用 auth Token

真实的 API 通常需要身份验证。常见的模式是登录一次,获取 Token,然后在其后的每个请求中发送该 Token。cy.request() 的链式调用非常适合处理这种场景。

发送登录请求,从响应 body 中读取 Token,然后在下一次调用中使用它:

describe('Authenticated API flow', () => {
  it('logs in and fetches a protected resource', () => {
    cy.request({
      method: 'POST',
      url: 'https://api.example.com/login',
      body: {
        email: 'user@example.com',
        password: 'secret'
      }
    }).then((loginResponse) => {
      expect(loginResponse.status).to.eq(200)
      const token = loginResponse.body.token

      cy.request({
        method: 'GET',
        url: 'https://api.example.com/profile',
        headers: {
          Authorization: `Bearer ${token}`
        }
      }).then((profileResponse) => {
        expect(profileResponse.status).to.eq(200)
        expect(profileResponse.body).to.have.property('email', 'user@example.com')
      })
    })
  })
})

如果多个测试需要相同的 Token,可以将登录逻辑移入 beforeEach 中,并使用 Cypress.env() 或封装的别名(alias)来存储 Token,以便每个测试都能获取到它。这样可以将 auth 设置集中在一处,而无需在每个 spec 中重复编写。

这种“先登录后使用”的模式与你在编写任何 API 集成测试流程时的逻辑完全一致,即一个调用产生的数据会传递给下一个调用。

使用 cy.intercept() 进行存根模拟

cy.request() 会向真实的 API 发送请求。有时你可能需要相反的操作:拦截前端应用发出的请求,并返回一个虚假的响应。这就是 cy.intercept() 的作用。

这里有一个重要的区别。cy.intercept() 仅拦截前端应用在浏览器中发出的请求。它不会拦截 cy.request(),因为 cy.request() 完全绕过了浏览器。当你需要测试 UI 如何对给定的 API 响应做出反应时,请使用 cy.intercept(),而不是在测试 API 本身时使用。

你可以监听(spy)请求、使用静态响应进行模拟(stub),或者等待它。要进行模拟,只需传入一个响应 object:

javascript cy.intercept('GET', '/api/users', { statusCode: 200, body: [{ id: 1, name: 'Ada' }] })

现在,任何浏览器对 /api/users 的请求都将返回这个固定的 payload。这使你能够测试在真实后端环境下难以触发的边缘情况,例如空列表、500 错误或慢响应。

要断言该请求已发生,可以使用 .as() 为其设置别名并等待它:

javascript it('shows an error banner when the API fails', () => { cy.intercept('GET', '/api/users', { statusCode: 500, body: { message: 'Server error' } }).as('getUsers')
cy.visit('/dashboard') cy.wait('@getUsers').its('response.statusCode').should('eq', 500) cy.contains('Something went wrong').should('be.visible') })

cy.wait('@getUsers') 这行代码会暂停测试,直到拦截的请求完成解析,然后将请求和响应传给你以进行断言。请务必在触发请求的操作之前设置拦截,否则它将无法捕获任何内容。如果你正在权衡何时模拟响应与 mock 整个服务,我们关于 API mocking 以及 API stubbing vs API mocking 的文章中详细分析了各自的利弊。

何时适合使用 Cypress 进行 API 测试,何时不适合

Cypress 非常适合嵌入在 UI 测试套件中的 API 检查。如果你已经在编写端到端测试,并且需要播种(seed)数据、验证 UI 依赖的接口,或者通过模拟响应来测试错误处理,cy.request()cy.intercept() 可以将所有内容集中在同一个地方。无需切换上下文,也无需引入第二个工具。

如果 Cypress 是你唯一的测试 runner,并且你不想添加其他依赖项,那么用它来进行少量独立的 API 冒烟测试也是完全可以的。

但如果是针对大型的、独立的 API 测试套件,情况就会变得有些尴尬。Cypress 是专为浏览器构建的,因此 API 测试只是在其上附加的一项功能,而非核心设计。随着测试套件的增长,会出现一些不足之处:

  • 没有可视化的请求构建器。每个请求都是代码。当你有数百个接口时,手动编写 header、body 和 query 参数会变得很慢。
  • 对于仅在 CI 中运行的 API 流水线来说较为逊色。Cypress 即使对于纯 API 运行也会启动浏览器上下文,这会带来不必要的开销。
  • 没有共享的 API 定义。请求的结构存在于你的测试文件中,而不是存在于团队可以在设计、文档和测试中复用的规范中。

这就是 API 原生工具更合适的地方。Apifox 是围绕 API 本身构建的:你只需设计一次接口,然后使用可视化请求构建器和可视化断言来针对它构建测试场景,常见情况下无需编写代码。请求、环境和 auth 都保存在共享工作区中,因此你的团队无需从测试文件中重新推导它们。

对于 CI,Apifox CLI 可以在任何能运行 Node 的步骤中无头运行你保存的测试场景:

npm install -g apifox-cli
apifox run \ --access-token "$APIFOXACCESSTOKEN" \ -t\ -e\ -r cli,html,junit 

-t 参数指向已保存的场景或套件,-e 选择一个环境,而 -r 挑选报告格式(clihtmljsonjunit)。JUnit 输出可以直接接入大多数 CI 仪表盘。添加 -d--iteration-data 可以使用数据文件来驱动测试,而 --upload-report 可以将结果推送回你的工作区。CLI 运行的是已保存的场景,它不是一个交互式的请求发送器。

一个好记的思考方式是:对于支持浏览器测试的 API 检查,使用 cy.request();当 API 测试是主要工作时,选择 API 原生工具。如果你正在对比各种选择,我们收集的在 CI/CD 中运行的 API 测试自动化工具以及更广泛的 30 个最佳 API 测试工具涵盖了这一领域。

常见问题

cy.request() 是在浏览器中运行吗?

不。cy.request() 运行在 Cypress Node 进程中,处于浏览器外部。这就是为什么它不会被 CORS 或同源策略阻止,以及为什么 cy.intercept() 无法拦截它的原因。如果你需要浏览器发出的请求,请通过你的应用触发它并使用 cy.intercept() 进行捕获。

如何在不导致测试失败的情况下测试 400 或 404 响应?

默认情况下,cy.request() 会在任何非 2xx 或非 3xx 状态码时报错导致测试失败。请在选项对象中设置 failOnStatusCode: false,然后自己对状态进行断言,例如 expect(response.status).to.eq(404)

我可以使用 cy.request() 在 UI 测试之前进行登录吗?

可以,而且这是一个很常见的模式。使用 cy.request() 向你的登录接口发送一个 POST 请求,从 response.body 中读取 Token,并将其存储(在 Cypress.env()localStorage 或 cookie 中),这样你的 UI 测试在开始时就已经完成了身份验证。这可以跳过登录表单并提高套件的运行速度。

cy.request() 和 cy.intercept() 之间有什么区别?

cy.request() 发送真实的 HTTP 请求并返回实际的响应,因此你可以用它来测试 API。cy.intercept() 则是监听或模拟前端在浏览器中发出的请求,因此你可以用它来控制 UI 接收到的内容。前者直接访问网络,后者则对网络请求进行拦截和塑造。

我应该使用 Cypress 还是专门的 API 测试工具?

当 API 检查是为了辅助你现有的浏览器测试套件时,可以使用 Cypress。但对于大型的独立 API 套件、仅在 CI 运行的 API 流水线,或者整个团队共同协作的共享 API 定义,像 Apifox 这样原生支持 API 的工具会更加合适。请参阅我们的 API 测试最佳实践以了解如何进行选择。

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。

如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用

Apifox

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案

获取专属报价与部署方案

icon 详细的私有化部署系统架构与安全白皮书
icon 针对您公司规模的专属报价单
icon 免费的 1v1 专属产品演示 (Demo) 机会
获取部署方案
* 提交后,我们的客户经理将在 1 个工作日内与您联系
林俊锋 企业微信
@Apifox 专属顾问
扫码备注: 私有化 + 公司名