如何在Cypress中使用mock-socket模拟Apollo的GraphQL subscription?
原因说明
你现有代码不生效的核心原因是:Apollo的WebSocketLink默认使用的WebSocket构造函数没有绑定到全局window对象,打包后的Apollo代码会直接引用模块作用域内的WebSocket实例,你对全局window.WebSocket的stub无法覆盖到这个内部引用。
可选解决方案
方案1:应用代码预留注入入口(最稳定,推荐)
Apollo的WebSocketLink构造参数原生支持自定义webSocketImpl,你可以在应用的Apollo Client初始化逻辑中增加测试场景的兼容逻辑:
// 应用内Apollo Client初始化代码 import { WebSocketLink } from "@apollo/client/link/ws"; const wsLink = new WebSocketLink({ uri: "ws://你的GraphQL订阅端点地址", options: { reconnect: true }, // 测试环境下优先使用Cypress注入的mock WebSocket实现 webSocketImpl: window.CYPRESS_MOCK_WEBSOCKET || WebSocket, });
对应的Cypress测试代码调整为:
import { Server, WebSocket as MockWebSocket } from 'mock-socket'; it('测试GraphQL订阅逻辑', () => { let mockServer; const WS_ENDPOINT = 'ws://你的GraphQL订阅端点地址'; cy.visit('/', { onBeforeLoad(window) { // 注入mock的WebSocket实现供Apollo使用 window.CYPRESS_MOCK_WEBSOCKET = MockWebSocket; // 启动mock socket服务 if(mockServer) mockServer.stop(); mockServer = new Server(WS_ENDPOINT); mockServer.on('connection', (socket) => { // 监听客户端请求,返回对应mock订阅数据 socket.on('message', (rawData) => { const req = JSON.parse(rawData); // 匹配对应的订阅操作返回mock数据,示例适配subscriptions-transport-ws协议 if(req.type === 'start' && req.payload.operationName === '你要测试的订阅操作名') { socket.send(JSON.stringify({ type: 'data', id: req.id, payload: { data: { // 你的mock返回数据 } } })) } }) }) } }) // 后续测试断言逻辑 })
方案2:无需修改应用代码,stub WebSocketLink构造函数
如果无法修改应用代码,你可以在onBeforeLoad阶段拦截WebSocketLink的构造逻辑,强制替换它的webSocketImpl参数:
- 若你的应用将Apollo相关依赖挂载到了全局
window对象,直接stub对应路径下的WebSocketLink构造函数即可 - 若应用是闭包打包的,你可以通过Cypress的
cy.intercept拦截加载Apollo相关JS包的请求,在返回的代码末尾注入stub逻辑,覆盖WebSocketLink的默认行为
方案3:使用Cypress原生WebSocket拦截
Cypress v12+ 已支持实验性的原生WebSocket拦截能力,开启experimentalWebsocketsSupport配置后,可直接用cy.intercept匹配WebSocket连接,模拟返回数据,无需引入第三方mock库。
注意事项
- 如果你使用的是
graphql-ws库实现的GraphQL over WebSocket新协议,需要调整mock返回的消息格式与新协议规范对齐,否则Apollo无法正常识别返回内容 - mock-socket的服务端点必须和应用内使用的订阅端点完全一致(协议、域名、端口、路径均相同)才能匹配到请求
内容的提问来源于stack exchange,提问作者Ofri Shani
相关产品推荐
相关产品推荐

