返回

Cypress+Pinia: 无GUI拦截API测试Store

vue.js

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 数据。

操作步骤:

  1. beforeEachbefore 块中添加 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 逻辑的单元测试。

操作步骤:

  1. 在 Store 中,将 API 调用逻辑封装到一个可注入的函数中。
  2. 在测试文件中,使用 Mock 函数替换掉真正的 API 调用函数。
  3. 直接调用 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" 问题,并根据实际需求选择合适的测试策略。记住,根据项目的复杂性进行选择!