Cypress+Pinia: 无GUI拦截API测试Store
2025-02-06 11:35:56
Cypress 与 Pinia:无 GUI 界面拦截 API 请求进行 Store 测试
在前端开发中,对 Pinia store 进行单元测试是保证应用稳定性和可靠性的关键环节。 通常情况下,我们会需要拦截 API 请求来模拟不同的服务器响应,从而覆盖各种测试场景。 本文将介绍如何在使用 Cypress 测试 Pinia store 时,拦截 API 请求,并在不访问 GUI 界面的情况下进行测试,从而避免了 "integration/undefined" 问题。
问题分析
Cypress 拦截 API 请求时,如果测试没有访问应用的 GUI 界面(例如使用 cy.visit()
),Cypress 可能会尝试在自身提供的 iframe 环境中处理请求。 这个 iframe 环境的 URL 通常类似于 http://localhost:8080/__cypress/iframes/...
,与应用自身的 URL (例如 http://localhost:8080/
)不同。 由于应用的代码并没有在 Cypress 的 iframe 环境中执行,导致拦截的请求返回了一个包含错误信息的 HTML 页面,其 <title>
标签包含了 "integration/undefined" 字符串,表明请求没有正确地路由到你的应用程序代码。
解决方案一:显式访问 GUI 界面
最直接的解决方案是在测试开始前,使用 cy.visit()
访问你的应用。 这样做确保了应用程序的代码在 Cypress 环境中正确地加载,并且 API 请求会被路由到应用自身处理,从而可以成功地被 Cypress 拦截并返回预期的 fixture 数据。
操作步骤:
- 在
beforeEach
或before
块中添加cy.visit()
,指向你的应用的 URL。
代码示例:
import { createPinia, setActivePinia } from 'pinia'
import { useRes2Store } from "@/store/resources/res2Store";
describe('Res2 Store', () => {
beforeEach(() => {
const pinia = createPinia()
setActivePinia(pinia)
// 显式访问 GUI 界面
cy.visit('http://localhost:8080'); // 修改为你的应用 URL
cy.fixture('res.json').as('res2')
cy.intercept('GET', '/api/resource1/1/resource2', {fixture: 'res.json'})
})
it('Call the tagStore action to get res2 items to a res1', () => {
const res2Store = useRes2Store()
cy.wrap(res2Store.getRes2(1)).then(() => {
cy.get('@res2').then((res2) => {
expect(res2Store.res).to.deep.equal(res2)
})
})
})
})
原理: cy.visit()
强制 Cypress 加载应用程序的完整环境,使后续的 API 请求能够正确地被路由到应用服务器并被拦截处理。
解决方案二:直接调用 Store Actions 进行测试
如果目标是只测试 Store Actions 的行为,而不依赖于特定的 UI 界面,则可以绕过 API 请求,直接 mock Store 内部的依赖项。 这可以通过依赖注入或模拟(mocking)来实现。 这种方案无需拦截 API 请求,更侧重于对 Store 逻辑的单元测试。
操作步骤:
- 在 Store 中,将 API 调用逻辑封装到一个可注入的函数中。
- 在测试文件中,使用 Mock 函数替换掉真正的 API 调用函数。
- 直接调用 Store 的 Action,验证其行为和状态。
代码示例:
首先,修改 res2Store
以允许 API 函数被注入:
// src/store/resources/res2Store.js
import { defineStore } from 'pinia'
import axios from 'axios'; // 确保已经安装 axios
export const useRes2Store = defineStore('res2', {
state: () => ({
res: null,
}),
actions: {
async getRes2(id, apiCall = async (id) => { // 允许注入 apiCall
const response = await axios.get(`/api/resource1/${id}/resource2`);
return response.data;
}) {
this.res = await apiCall(id);
},
},
})
然后,编写测试用例,使用 Cypress 的 cy.stub()
创建 API 调用的模拟:
import { createPinia, setActivePinia } from 'pinia'
import { useRes2Store } from "@/store/resources/res2Store";
describe('Res2 Store', () => {
beforeEach(() => {
const pinia = createPinia()
setActivePinia(pinia)
})
it('Call the tagStore action to get res2 items to a res1', async () => {
const res2Store = useRes2Store();
// Mock API 调用
cy.fixture('res.json').then((res2) => {
const apiCallStub = cy.stub().resolves(res2); // 使用 resolves 返回 promise
res2Store.getRes2(1, apiCallStub); // 将 mock 传递给 action
// 确保 action 被调用, 并且验证state
cy.wrap(res2Store).then(()=>{ // 因为getRes2 是异步的需要等待。
expect(apiCallStub).to.be.calledWith(1);
expect(res2Store.res).to.deep.equal(res2);
});
})
})
})
原理: 通过模拟 API 调用,测试用例不再依赖于实际的 API 端点,而是直接验证 Store Action 在特定输入下的行为。 这样做可以将 Store 的逻辑与外部依赖隔离开来,提高测试的可靠性和速度。
安全建议:
- 始终验证模拟函数的参数,以确保 Store Action 在内部正确地处理数据。
- 在 Store Action 中添加错误处理逻辑,并使用模拟来测试各种错误情况。
通过上述方案,可以解决 Cypress 测试 Pinia store 时遇到的 "integration/undefined" 问题,并根据实际需求选择合适的测试策略。记住,根据项目的复杂性进行选择!