Cypress 组件测试实战:在 @cypress/vue 中 Stub axios 默认导出的完整方法
本文基于 Cypress 官方仓库中 npm/vue 包的 advanced/mocking-axios 示例文档,讲解 Vue 组件测试中如何对 node_modules 里的 axios 进行方法级打桩:为什么必须 stub 与 spec 文件相同的模块实例、axios.get 的 stub 写法与断言方式,以及如何结合 JSON fixture 文件构造更真实的 mock 响应。读完你可以掌握一套可直接复制到项目中运行的 axios mock 组件测试方案。
场景背景:axios 是真实依赖,不能依赖网络
在 @cypress/vue(Cypress 官方 Vue 组件测试挂载库)的组件测试中,组件代码和测试代码运行在同一个浏览器 bundle 上下文里。示例中的 Users.vue 直接 import axios from 'axios',在 created 钩子中请求远端接口:
// Users.vue(节选)
import axios from 'axios'
export default {
data () {
return {
users: [],
}
},
// Fetches posts when the component is created.
created () {
axios.get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then((response) => {
// JSON responses are automatically parsed.
this.users = response.data
})
},
}
这个组件的渲染完全依赖 axios.get 的返回值:users 有数据时模板才渲染 <ul> 列表。如果组件测试中不去拦截这个请求,测试就会不稳定地依赖外部 API。原始文档(README.md)对此的定位很明确:Axios 安装在 node_modules 中,并被 Vue 组件直接使用,因此正确做法不是 mock 网络层,而是对模块的方法进行 stub。
核心思路:stub 默认导出上的方法
原始文档给出的关键结论是:
HTTP helpers live on the default export. Import the default and call
axios.get, then stub that method on the same module instance your spec imports.
也就是说,axios 的 get/post 等 HTTP 辅助方法挂在默认导出对象上。组件里写 import axios from 'axios' 然后 axios.get(...);那么 spec 里也必须 import Axios from 'axios'(导入同一个默认导出),再在这个默认导出上 stub get。
这里“same module instance”是成败关键:在组件测试中,spec 文件和被挂载的组件由同一个 dev server 打包进同一个运行环境,模块会被 bundler 的模块缓存共享——spec 导入的 Axios 与组件里使用的 axios 是同一对象,因此在其中一个引用上打桩,另一处的调用自然命中桩。反过来,如果组件通过具名导入(如 import { get } from 'axios')或自行创建实例(axios.create()),默认导出上的 stub 就无法覆盖,需要另行处理。从示例代码结构看,本示例选择的就是默认导出 + 方法打桩这一条路径,这也是 axios 最常见的用法。
stub 的基本形态(来自原文档):
// component code
import axios from 'axios'
axios.get('...').then(...)
// spec file: import the default and stub .get
import Axios from 'axios'
cy.stub(Axios, 'get').resolves({
data: [
{
id: 101,
name: 'Test User',
},
],
})
注意 resolves 的值是完整的 axios 响应对象:组件里读取的是 response.data,所以 mock 时必须包一层 data,而不是直接把数组传进去——否则组件中 this.users = response.data 拿到 undefined,断言必然失败。
完整 spec 示例:两种 mock 数据源
仓库中的 Users.cy.js 给出了两个完整测试用例,完整代码如下:
/// <reference types="cypress" />
import { mount } from '@cypress/vue'
import Users from './Users.vue'
// we can load list of mock users straight from JSON file
import mockUsers from './user.list.json'
import Axios from 'axios'
describe('Mocking axios.get with default import', () => {
it('renders mocked data', () => {
cy.stub(Axios, 'get')
.resolves({
data: [
{
id: 101,
name: 'Test User',
},
],
})
.as('get')
mount(Users)
// mock response is used
cy.get('li').should('have.length', 1)
cy.get('@get').should('have.been.calledOnce')
})
it('stubs with JSON loaded from fixture file', () => {
cy.stub(Axios, 'get')
.resolves({
data: mockUsers,
})
.as('get')
mount(Users)
// mock response is used
cy.get('li').should('have.length', 2)
cy.get('@get').should('have.been.calledOnce')
})
})
两个用例演示了两种数据源与两类断言:
- 内联 mock 数据:直接在
resolves里写死响应,断言li长度为 1; - 从 JSON fixture 文件加载:
import mockUsers from './user.list.json',mock 数据放在 user.list.json 中(内容为两条用户:101 First User、102 Second User),断言li长度为 2。把数据抽到独立 JSON 文件的价值在于:测试数据与断言逻辑分离,便于多人维护与复用; - 调用次数断言:两个用例都用
.as('get')给 stub 起别名,再用cy.get('@get').should('have.been.calledOnce')验证组件确实发起且仅发起了一次axios.get。这同时验证了「组件走的是桩,而不是真实网络」。
运行配置:在 @cypress/vue 项目中跑起来
该示例位于 npm/vue 包内,其组件测试由 npm/vue/cypress.config.ts 驱动,关键配置如下:
import { defineConfig } from 'cypress'
export default defineConfig({
'viewportWidth': 500,
'viewportHeight': 500,
'responseTimeout': 2500,
'projectId': '134ej7',
'e2e': {
'supportFile': false,
},
'component': {
experimentalSingleTabRunMode: true,
excludeSpecPattern: 'examples/**/*',
devServer: {
bundler: 'vite',
framework: 'vue',
},
},
})
要点:
component.devServer指定bundler: 'vite'、framework: 'vue'——正是这个 dev server 把 spec 与组件打进同一个 bundle,从而保证 spec 里的Axios与组件里的axios是同一模块实例,stub 才能生效;viewportWidth/Height为 500x500,与上面截图中组件视口尺寸一致;- 测试 spec 采用
.cy.js命名(Users.cy.js),位于cypress/component/下,符合 Cypress 组件测试的默认 spec 查找规则。
依赖方面,npm/vue/package.json 中 axios(^1.15.1)与 vue(3.2.47)均为 devDependencies,说明示例里的 axios 是测试项目本地安装的普通 npm 依赖。运行命令也定义在该文件里:
# 在 npm/vue 目录下
yarn cy:run # 等价于 node ../../scripts/cypress.js run --component --project ${PWD}
yarn cy:open # 打开 Runner 交互查看
与同类 mock 手段的边界
同一目录 cypress/component/advanced/ 下还有 mocking-fetch、mocking-imports 等相邻示例,可以据此判断本文方案的适用边界:
- 当请求走的是 axios 这类独立 npm 模块时,用本文的方法:stub 模块默认导出上的方法,粒度最细,且能断言调用次数与参数;
- 当请求走的是浏览器原生
fetch时,由于fetch挂在window上,可以直接 mockwindow.fetch,见 mocking-fetch 示例; - 当组件依赖的是本地模块/组件而非网络库时,则属于 mocking-imports 与 mocking-components 覆盖的范畴。
小结
对「组件直接 import axios」的 Vue 项目,Cypress 组件测试的标准打法可以归纳为三步:
- spec 中
import Axios from 'axios',保证拿到与组件相同的模块实例(依赖 vite/webpack 组件测试 dev server 的模块共享); cy.stub(Axios, 'get').resolves({ data: ... }),注意 mock 的是完整响应对象,data字段不可省略,并可用.as()登记别名;- 用渲染断言(
li数量)验证数据确实来自桩,用have.been.calledOnce验证组件只发起一次预期请求;mock 数据可内联,也可抽到 JSON fixture 文件(如 user.list.json)便于维护。
这套做法让组件测试完全不触碰真实网络,同时保留了 axios 响应的真实数据结构,是 @cypress/vue 组件测试中处理 HTTP 依赖的推荐姿势。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
