ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apollo Client 测试链路深度解析:MockLink 与 MockSubscriptionLink 完整 API 指南

Apollo Client 测试链路深度解析:MockLink 与 MockSubscriptionLink 完整 API 指南 前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载导读在 Apollo Client 的单元测试与集成测试中如何在不启动真实 GraphQL 服务器的情况下验证客户端行为是一个高频且关键的工程问题。apollo/client/testing子包提供了MockLink请求/响应式 Mock与MockSubscriptionLink可手动推送订阅事件的 Mock两类可编程的ApolloLink实现配合realisticDelay延迟工具可以让开发者以近乎真实的网络行为驱动测试。本文以仓库根目录.api-reports/api-report-testing.api.mdAPI Extractor 生成的apollo/client/testing公共 API 报告为骨架结合 src/testing/core/mocking/mockLink.ts 与 src/testing/core/mocking/mockSubscriptionLink.ts 的源码实现逐项剖析这些 API 的签名、类型约束、匹配语义与底层调用链并给出可直接运行的测试写法。读完本文你将能精确使用MockLink的变量匹配、maxUsageCount消耗、delay/Infinity悬挂等能力也能用MockSubscriptionLink手动编排订阅流。一、入口与整体结构apollo/client/testing导出了什么API 报告的开头明确说明该文件由 API Extractorexport type { MockedRequest, MockedResponse, MockLinkOptions, ResultFunction, } from ./core/types/deprecated.js; export { MockLink, realisticDelay } from ./core/mocking/mockLink.js; export { MockSubscriptionLink } from ./core/mocking/mockSubscriptionLink.js;由此可以确认公共面由三类组成类MockLink、MockSubscriptionLink均继承自ApolloLink函数realisticDelay生成模拟真实网络时延的延迟函数类型MockedRequest、MockedResponse、MockLinkOptions、ResultFunction这四个在 src/testing/core/types/deprecated.ts 中被标记为deprecated推荐改用MockLink命名空间下的同名类型详见下文第五部分。API 报告还记录了两个internal类型CovariantUnaryFunction与VariableMatcher。它们虽不出现在公共导出中却是ResultFunction与MockedRequest.variables类型推导的基石见 src/testing/core/mocking/mockLink.ts理解它们有助于读懂 mock 响应函数的签名。二、MockLink基于请求-响应队列的链路 MockMockLink是最核心的测试组件它接收一个按顺序排列的MockedResponse数组每当request(operation)被调用时就从队列中查找并消耗一个匹配的 mock。API 报告给出了完整签名export class MockLink extends ApolloLink { constructor( mockedResponses: ReadonlyArray MockLink.MockedResponseRecordstring, any, Recordstring, any , options?: MockLink.Options ); addMockedResponse(mockedResponse: MockLink.MockedResponse): void; static defaultOptions: MockLink.DefaultOptions; operation: ApolloLink.Operation; request(operation: ApolloLink.Operation): ObservableApolloLink.Result; showWarnings: boolean; }2.1 构造与选项Options与静态defaultOptionsMockLink.Options仅有两个可选字段见 mockLink.ts字段类型默认值语义showWarningsbooleantrue当请求没有匹配到任何 mock 时是否向console.warn输出诊断信息defaultOptionsDefaultOptions继承MockLink.defaultOptions未在单条响应上显式指定delay时使用的兜底延迟DefaultOptions只有一个成员delay?: MockLink.Delay而MockLink.Delay number | DelayFunction其中DelayFunction (operation: ApolloLink.Operation) number。也就是说延迟既可以是固定毫秒数也可以是根据当前 operation含query、variables、operationName等动态计算的函数。静态成员MockLink.defaultOptions的默认值是{ delay: realisticDelay() }见 mockLink.ts——这意味着即使你完全不配置delayMockLink 也会默认产生一个 20~50ms 的随机时延。构造时defaultOptions.delay ?? realisticDelay()的合并逻辑mockLink.ts保证了这个静态默认值可以被实例级配置覆盖。从 src/testing/core/mocking/tests/mockLink.ts 的测试可以看到两种用法// 实例级全部 mock 延迟 100ms const link new MockLink([{ request: { query }, result: { data: { a: a } } }], { defaultOptions: { delay: 100 }, }); // 全局级修改静态默认值影响之后创建的所有实例 MockLink.defaultOptions { delay: 50 };2.2 请求匹配query 归一化 变量匹配request的匹配流程mockLink.ts是整个 MockLink 的精髓分两步查询文档归一化getMockedResponses会把 mock 的query先经过addTypenameToDocument补全__typename选择集再用print序列化为字符串作为分组 keymockLink.ts。这保证同一查询的不同书写格式例如换行、空格的差异能被视为同一个 key但不同查询之间互不干扰。变量匹配在同一个 key 下的 mock 队列中按顺序查找命中条件为若 mock 的variables是一个函数即VariableMatcher则以variables(operation.variables)的布尔返回值作为是否匹配否则将 mock 变量与查询默认变量合并后{...getDefaultValues(operationDefinition), ...request.variables}见 mockLink.ts与请求变量做深度相等比较使用wry/equality的equal。// 方式一精确变量匹配注意会与 query 中的默认值合并比较 const link new MockLink([ { request: { query, variables: { id: 1 } }, result: { data: { a: a } }, }, ]); // 方式二自定义断言函数匹配VariableMatcher 模式 const link new MockLink([ { request: { query, variables: (vars) vars!.id 1, // 返回 boolean }, result: { data: { a: a } }, }, ]);匹配失败诊断如果没有命中MockLink 会构造一条详细错误信息打印查询原文、请求变量以及每个 mock 的变量/函数名见 mockLink.ts在showWarnings为true时先console.warn随后返回一个以asapScheduler调度的throwErrorObservable将错误以Error(No more mocked responses for the query: ...)的形式抛给下游。2.3 响应消耗maxUsageCount与队列消费API 报告显示MockedResponse携带maxUsageCount?: number。源码在 mockLink.ts 中实现的语义是每条 mock 归一化时maxUsageCount ?? 1即默认只能用一次命中后若maxUsageCount 1则自减并保留在队列中否则从队列splice移除。这带来两个重要行为同一查询多次执行需要多条 mock或把maxUsageCount调大一旦消耗完毕后续请求会落入第 2.2 节的“无更多 mock”报错分支从而在测试中暴露出“mock 数量不足”的配置问题。// 允许同一响应被命中两次 const link new MockLink([ { request: { query }, result: { data: { a: a } }, maxUsageCount: 2, }, ]);2.4 结果与错误result/error/delay的三元组合API 报告定义的MockedResponse结构mockLink.ts为interface MockedResponse out TData Recordstring, any, out TVariables extends OperationVariables Recordstring, any { request: MockedRequestTVariables; maxUsageCount?: number; result?: | ApolloLink.ResultUnmaskedTData | ResultFunctionApolloLink.ResultUnmaskedTData, TVariables; error?: Error; delay?: number | MockLink.DelayFunction; }其中result的联合类型值得注意直接给对象则原样下发给ResultFunctionT, V (variables: V) T形式的函数则在响应时以operation.variables为入参调用mockLink.ts适合根据请求变量动态生成响应数据result与error互斥若同时给出了errorrequest处理时优先走observer.error(matched.error)分支UnmaskedTData来自 src/maskingapollo/client/masking意味着result的数据在类型层面已经被视为“已解除 data masking”的形态与当前仓库的 masking 能力对齐。延迟处理的细节mockLink.tsdelay为函数时先调用得到毫秒数再setTimeout后在宏任务中依次observer.next与observer.completedelay: Infinity是特例此时允许result与error都缺省MockLink 返回一个永不发射、永不结束的空 Observable模拟“请求一直挂起”的场景如测 loading 态与竞态取消若delay是有限数值却又没有result/error则直接抛错错误信息为Mocked response should contain either result, error or a delay of Infinity。这一点被 src/testing/core/mocking/tests/mockLink.ts 的测试用例明确覆盖且测试中还标注了MAXIMUM_DELAY 0x7fffffff超过 32 位有符号整数范围的 delay 会导致setTimeout立即触发因此Infinity是唯一的“永久悬挂”表达方式。// 永久挂起的 mock测试 loading / abort 场景 const link new MockLink([ { request: { query }, delay: Infinity }, ]);2.5addMockedResponse与运行时校验除构造时批量传入外addMockedResponse允许在运行时追加 mockmockLink.ts。每条响应在加入队列前会经过validateMockedResponse校验mockLink.tscheckDocument(request.query)校验 GraphQL 文档合法性maxUsageCount必须大于 0invariant((mock.maxUsageCount ?? 1) 0, ...)否则抛出 invariant 错误。同时normalizeMockedResponse还会对请求文档做一次“服务端化”处理getServerQuery见 mockLink.ts依次剥离connection、nonreactive、unmask客户端指令与client指令并保证查询中至少保留一个非 client 字段invariant(serverQuery, Cannot mock a client-only query. ...)。这意味着纯 client-only 的查询无法用 MockLink 直接 mock这是有意为之的约束。2.6 暴露的只读现场operationrequest的第一步会把当前 operation 保存在实例的operation属性上mockLink.ts测试中可以直接读取link.operation.variables等字段断言“客户端实际发起了什么请求”这与request返回ObservableApolloLink.Result的链路式设计配合使用。三、realisticDelay模拟真实网络时延的工具函数API 报告给出export function realisticDelay( { min, max }?: { min?: number; max?: number } ): MockLink.DelayFunction;源码实现mockLink.ts非常精简export function realisticDelay({ min 20, max 50 } {}): MockLink.DelayFunction { invariant(max min, realisticDelay: min must be less than max); return () Math.floor(Math.random() * (max - min) min); }要点默认在[20, 50) 毫秒区间内均匀随机取值左闭右开传入参数时要求max min否则抛 invariant 错误返回的是一个DelayFunction因此可以嵌入到MockLink.defaultOptions.delay、Options.defaultOptions.delay或单条MockedResponse.delay的任意一层测试中用realisticDelay({ min: 50, max: 100 })可以放大随机区间验证“尚未发射”与“已发射”两个时间窗mockLink.ts 测试。import { MockLink, realisticDelay } from apollo/client/testing; const link new MockLink( [{ request: { query }, result: { data: { a: a } } }], { defaultOptions: { delay: realisticDelay({ min: 100, max: 300 }) } } );四、MockSubscriptionLink可手动编排的订阅 MockGraphQL 订阅的核心特征是服务端主动、多次推送这与查询/变更的“一次响应一次完成”模型完全不同。MockSubscriptionLink为此提供了一套“录制 手动触发”的 API源码见 src/testing/core/mocking/mockSubscriptionLink.ts。4.1 类型与成员API 报告中的完整面export class MockSubscriptionLink extends ApolloLink { operation?: ApolloLink.Operation; setups: any[]; unsubscribers: any[]; request(operation: ApolloLink.Operation): ObservableFormattedExecutionResultRecordstring, any, Recordstring, any; simulateResult(result: MockSubscriptionLink.Result, complete?: boolean): void; simulateComplete(): void; onSetup(listener: any): void; onUnsubscribe(listener: any): void; } export namespace MockSubscriptionLink { export interface Result { result?: ApolloLink.Result; error?: Error; delay?: number; } }4.2 工作原理Observable 订阅生命周期request(operation)mockSubscriptionLink.ts保存 operation 后返回一个 rxjsObservable只有当下游真正subscribe时才会把观察者推入内部observers数组并执行所有setups监听器而一旦取消订阅则执行所有unsubscribers监听器。这一点非常符合 Apollo Link 的惰性求值语义——src/tests/graphqlSubscriptions.ts 的测试专门验证了“未调用subscribe前onSetup不会被触发”的惰性行为。import { MockSubscriptionLink } from apollo/client/testing; const link new MockSubscriptionLink(); link.onSetup(() console.log(subscription established)); link.onUnsubscribe(() console.log(subscription torn down)); const client new ApolloClient({ link, cache: new InMemoryCache() }); const obs client.subscribe({ query: gqlsubscription UserInfo { user { name } }, }); obs.subscribe(); // 此时 setups 被调用4.3 手动推送simulateResult与simulateCompletesimulateResultmockSubscriptionLink.ts模拟“服务端推来一条事件”通过setTimeout(..., result.delay || 0)支持延迟推送delay可选对当前所有已订阅的 observer 依次执行result.result存在则observer.nextresult.error存在则observer.errorcomplete参数为true则observer.complete若此刻没有任何活跃订阅observers.length 0会抛出Error(subscription torn down)——这正是 src/tests/graphqlSubscriptions.ts 中“先client.subscribe建立流再link.simulateResult(results[0])推送、断言发射”这一标准循环的底层机制。simulateComplete则直接对所有 observer 调complete模拟订阅正常关闭。const link new MockSubscriptionLink(); const results [ { result: { data: { user: { __typename: User, name: A } } }, delay: 10 }, { result: { data: { user: { __typename: User, name: B } } }, delay: 10 }, ]; // 在测试中逐步推送 link.simulateResult(results[0]); link.simulateResult(results[1]); link.simulateComplete();这种“测试代码掌控推送节奏”的能力让开发者可以精确断言“收到第 N 条推送时 UI 处于什么状态”也可以穿插simulateResult({ error })验证错误处理路径。4.4 与 ApolloClient 的集成方式在 src/tests/graphqlSubscriptions.ts 等测试中标准用法是将MockSubscriptionLink直接作为ApolloClient的link传入const client new ApolloClient({ link: new MockSubscriptionLink(), cache: new InMemoryCache() });由于MockSubscriptionLink继承自ApolloLink见 src/link/core/ApolloLink.ts它可以被concat/from组合进更复杂的链路也可以嵌入MockLink之外的自定义链路中。同一仓库的useSubscription、useSuspenseQuery、useBackgroundQuery、createQueryPreloader等大量 React hooks 测试都复用了这一组件是订阅相关测试的事实标准工具。五、类型别名与废弃标记MockedResponse等顶层类型的去留API 报告明确标注了四个顶层类型为public deprecatedMockedRequest、MockedResponse、MockLinkOptions、ResultFunction。在 src/testing/core/types/deprecated.ts 中它们的定义均为一行别名/** deprecated Use MockLink.MockedRequest instead */ export type MockedRequestTVariables ... MockLink.MockedRequestTVariables; /** deprecated Use MockLink.MockedResponse instead */ export type MockedResponseTData ... MockLink.MockedResponseTData, TVariables; /** deprecated Use MockLink.Options instead */ export type MockLinkOptions MockLink.Options; /** deprecated Use MockLink.ResultFunction instead */ export type ResultFunctionT, V Recordstring, any MockLink.ResultFunctionT, V;结论很清晰新代码请优先使用命名空间限定形式MockLink.MockedResponse...、MockLink.Options、MockLink.ResultFunction...与MockLink.DelayFunction它们语义自包含且不会被废弃旧的顶层别名为了兼容历史版本仍会继续导出这也是apollo/client/testing公共 API 的一部分但文档注释与类型层面均已提示迁移。同样在 API 报告中CovariantUnaryFunction被标记为internal deprecated——它不参与公共导出只作为ResultFunction与VariableMatcher的底层构建块利用“取对象方法类型”的技巧把参数协变语义编码进类型读者无需直接使用。六、从测试用例反推使用模式快速上手的三个范式结合 src/testing/core/mocking/tests/mockLink.ts 与仓库内各 hooks 测试可以归纳出三种高频使用范式范式一顺序响应队列query/mutation 的多次调用const link new MockLink([ { request: { query: q1 }, result: { data: { a: a } } }, { request: { query: q2 }, result: { data: { b: b } } }, // 同一 query 第二次执行 { request: { query: q1 }, result: { data: { a: a2 } } }, ]);MockLink 按“query key 变量匹配 队列顺序”精确分配天然支持多操作交替的场景。范式二错误注入与延迟悬挂// 注入网络错误 { request: { query }, error: new Error(network down) }, // 永久悬挂测 loading / abort { request: { query }, delay: Infinity },范式三订阅流的定时推送与ObservableStream配合const link new MockSubscriptionLink(); const stream new ObservableStream(client.subscribe({ query })); link.simulateResult({ result: { data: { user: { __typename: User, name: A } } } }); await expect(stream).toEmitTypedValue({ data: { user: { __typename: User, name: A } } }); link.simulateComplete();ObservableStream与toEmitTypedValue/toComplete等断言匹配器来自 src/testing/internal 与 src/testing/matchers它们是仓库为测试场景配套的流式断言基础设施。七、总结与进一步阅读apollo/client/testing的 API 设计遵循了“Apollo Link 协议之上叠加可编程性”的原则MockLink用一个有序、可消耗、可延迟、可报错的响应队列把服务端行为建模成确定性状态机MockSubscriptionLink把推送权交给测试代码realisticDelay则在两者之间提供默认的“真实感”。其公共类型含废弃别名由 API Extractor 统一维护在 .api-reports/api-report-testing.api.md源码实现与测试分别位于 src/testing/core/mocking/mockLink.ts、src/testing/core/mocking/mockSubscriptionLink.ts 及其 测试目录而 src/testing/index.ts 定义了最终导出的边界。如果你想进一步探究React 侧更高层的MockedProvider封装与断言匹配器见 src/testing/react/MockedProvider.tsx 与 src/testing/matchers/index.ts订阅相关的端到端测试范例见 src/tests/graphqlSubscriptions.ts同一 API 报告体系下的其他入口cache、react、link 等可在 .api-reports 目录下按文件名对照查阅。赞分享前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载相关推荐Apollo Client 错误体系全解析apollo/client/errors 模块 API 深度指南Apollo Client 错误体系全解析apollo/client/errors 模块 API 深度指南 Apollo Client 将运行时可能遇到的错前端GraphQLApollo Client 内部测试工具包 apollo/client/testing/internal API 全面解析Apollo Client 内部测试工具包 apollo/client/testing/internal API 全面解析 apollo/client/te前端GraphQLApollo Client 批量请求链路 BatchHttpLink 完整指南源码级解析与实战配置Apollo Client 批量请求链路 BatchHttpLink 完整指南源码级解析与实战配置 导读 本文围绕 Apollo Client 的 apol前端GraphQL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表