
前端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點擊查看免費下載導(dǎo)讀在 Apollo Client 的單元測試與集成測試中如何在不啟動真實 GraphQL 服務(wù)器的情況下驗證客戶端行為是一個高頻且關(guān)鍵的工程問題。apollo/client/testing子包提供了MockLink請求/響應(yīng)式 Mock與MockSubscriptionLink可手動推送訂閱事件的 Mock兩類可編程的ApolloLink實現(xiàn)配合realisticDelay延遲工具可以讓開發(fā)者以近乎真實的網(wǎng)絡(luò)行為驅(qū)動測試。本文以倉庫根目錄.api-reports/api-report-testing.api.mdAPI Extractor 生成的apollo/client/testing公共 API 報告為骨架結(jié)合 src/testing/core/mocking/mockLink.ts 與 src/testing/core/mocking/mockSubscriptionLink.ts 的源碼實現(xiàn)逐項剖析這些 API 的簽名、類型約束、匹配語義與底層調(diào)用鏈并給出可直接運行的測試寫法。讀完本文你將能精確使用MockLink的變量匹配、maxUsageCount消耗、delay/Infinity懸掛等能力也能用MockSubscriptionLink手動編排訂閱流。一、入口與整體結(jié)構(gòu)apollo/client/testing導(dǎo)出了什么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函數(shù)realisticDelay生成模擬真實網(wǎng)絡(luò)時延的延遲函數(shù)類型MockedRequest、MockedResponse、MockLinkOptions、ResultFunction這四個在 src/testing/core/types/deprecated.ts 中被標記為deprecated推薦改用MockLink命名空間下的同名類型詳見下文第五部分。API 報告還記錄了兩個internal類型CovariantUnaryFunction與VariableMatcher。它們雖不出現(xiàn)在公共導(dǎo)出中卻是ResultFunction與MockedRequest.variables類型推導(dǎo)的基石見 src/testing/core/mocking/mockLink.ts理解它們有助于讀懂 mock 響應(yīng)函數(shù)的簽名。二、MockLink基于請求-響應(yīng)隊列的鏈路 MockMockLink是最核心的測試組件它接收一個按順序排列的MockedResponse數(shù)組每當(dāng)request(operation)被調(diào)用時就從隊列中查找并消耗一個匹配的 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 構(gòu)造與選項Options與靜態(tài)defaultOptionsMockLink.Options僅有兩個可選字段見 mockLink.ts字段類型默認值語義showWarningsbooleantrue當(dāng)請求沒有匹配到任何 mock 時是否向console.warn輸出診斷信息defaultOptionsDefaultOptions繼承MockLink.defaultOptions未在單條響應(yīng)上顯式指定delay時使用的兜底延遲DefaultOptions只有一個成員delay?: MockLink.Delay而MockLink.Delay number | DelayFunction其中DelayFunction (operation: ApolloLink.Operation) number。也就是說延遲既可以是固定毫秒數(shù)也可以是根據(jù)當(dāng)前 operation含query、variables、operationName等動態(tài)計算的函數(shù)。靜態(tài)成員MockLink.defaultOptions的默認值是{ delay: realisticDelay() }見 mockLink.ts——這意味著即使你完全不配置delayMockLink 也會默認產(chǎn)生一個 20~50ms 的隨機時延。構(gòu)造時defaultOptions.delay ?? realisticDelay()的合并邏輯mockLink.ts保證了這個靜態(tài)默認值可以被實例級配置覆蓋。從 src/testing/core/mocking/tests/mockLink.ts 的測試可以看到兩種用法// 實例級全部 mock 延遲 100ms const link new MockLink([{ request: { query }, result: { data: { a: a } } }], { defaultOptions: { delay: 100 }, }); // 全局級修改靜態(tài)默認值影響之后創(chuàng)建的所有實例 MockLink.defaultOptions { delay: 50 };2.2 請求匹配query 歸一化 變量匹配request的匹配流程mockLink.ts是整個 MockLink 的精髓分兩步查詢文檔歸一化getMockedResponses會把 mock 的query先經(jīng)過addTypenameToDocument補全__typename選擇集再用print序列化為字符串作為分組 keymockLink.ts。這保證同一查詢的不同書寫格式例如換行、空格的差異能被視為同一個 key但不同查詢之間互不干擾。變量匹配在同一個 key 下的 mock 隊列中按順序查找命中條件為若 mock 的variables是一個函數(shù)即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 } }, }, ]); // 方式二自定義斷言函數(shù)匹配VariableMatcher 模式 const link new MockLink([ { request: { query, variables: (vars) vars!.id 1, // 返回 boolean }, result: { data: { a: a } }, }, ]);匹配失敗診斷如果沒有命中MockLink 會構(gòu)造一條詳細錯誤信息打印查詢原文、請求變量以及每個 mock 的變量/函數(shù)名見 mockLink.ts在showWarnings為true時先console.warn隨后返回一個以asapScheduler調(diào)度的throwErrorObservable將錯誤以Error(No more mocked responses for the query: ...)的形式拋給下游。2.3 響應(yīng)消耗maxUsageCount與隊列消費API 報告顯示MockedResponse攜帶maxUsageCount?: number。源碼在 mockLink.ts 中實現(xiàn)的語義是每條 mock 歸一化時maxUsageCount ?? 1即默認只能用一次命中后若maxUsageCount 1則自減并保留在隊列中否則從隊列splice移除。這帶來兩個重要行為同一查詢多次執(zhí)行需要多條 mock或把maxUsageCount調(diào)大一旦消耗完畢后續(xù)請求會落入第 2.2 節(jié)的“無更多 mock”報錯分支從而在測試中暴露出“mock 數(shù)量不足”的配置問題。// 允許同一響應(yīng)被命中兩次 const link new MockLink([ { request: { query }, result: { data: { a: a } }, maxUsageCount: 2, }, ]);2.4 結(jié)果與錯誤result/error/delay的三元組合API 報告定義的MockedResponse結(jié)構(gòu)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的聯(lián)合類型值得注意直接給對象則原樣下發(fā)給ResultFunctionT, V (variables: V) T形式的函數(shù)則在響應(yīng)時以operation.variables為入?yún)⒄{(diào)用mockLink.ts適合根據(jù)請求變量動態(tài)生成響應(yīng)數(shù)據(jù)result與error互斥若同時給出了errorrequest處理時優(yōu)先走observer.error(matched.error)分支UnmaskedTData來自 src/maskingapollo/client/masking意味著result的數(shù)據(jù)在類型層面已經(jīng)被視為“已解除 data masking”的形態(tài)與當(dāng)前倉庫的 masking 能力對齊。延遲處理的細節(jié)mockLink.tsdelay為函數(shù)時先調(diào)用得到毫秒數(shù)再setTimeout后在宏任務(wù)中依次observer.next與observer.completedelay: Infinity是特例此時允許result與error都缺省MockLink 返回一個永不發(fā)射、永不結(jié)束的空 Observable模擬“請求一直掛起”的場景如測 loading 態(tài)與競態(tài)取消若delay是有限數(shù)值卻又沒有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 位有符號整數(shù)范圍的 delay 會導(dǎo)致setTimeout立即觸發(fā)因此Infinity是唯一的“永久懸掛”表達方式。// 永久掛起的 mock測試 loading / abort 場景 const link new MockLink([ { request: { query }, delay: Infinity }, ]);2.5addMockedResponse與運行時校驗除構(gòu)造時批量傳入外addMockedResponse允許在運行時追加 mockmockLink.ts。每條響應(yīng)在加入隊列前會經(jīng)過validateMockedResponse校驗mockLink.tscheckDocument(request.query)校驗 GraphQL 文檔合法性maxUsageCount必須大于 0invariant((mock.maxUsageCount ?? 1) 0, ...)否則拋出 invariant 錯誤。同時normalizeMockedResponse還會對請求文檔做一次“服務(wù)端化”處理getServerQuery見 mockLink.ts依次剝離connection、nonreactive、unmask客戶端指令與client指令并保證查詢中至少保留一個非 client 字段invariant(serverQuery, Cannot mock a client-only query. ...)。這意味著純 client-only 的查詢無法用 MockLink 直接 mock這是有意為之的約束。2.6 暴露的只讀現(xiàn)場operationrequest的第一步會把當(dāng)前 operation 保存在實例的operation屬性上mockLink.ts測試中可以直接讀取link.operation.variables等字段斷言“客戶端實際發(fā)起了什么請求”這與request返回ObservableApolloLink.Result的鏈路式設(shè)計配合使用。三、realisticDelay模擬真實網(wǎng)絡(luò)時延的工具函數(shù)API 報告給出export function realisticDelay( { min, max }?: { min?: number; max?: number } ): MockLink.DelayFunction;源碼實現(xiàn)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) 毫秒?yún)^(qū)間內(nèi)均勻隨機取值左閉右開傳入?yún)?shù)時要求max min否則拋 invariant 錯誤返回的是一個DelayFunction因此可以嵌入到MockLink.defaultOptions.delay、Options.defaultOptions.delay或單條MockedResponse.delay的任意一層測試中用realisticDelay({ min: 50, max: 100 })可以放大隨機區(qū)間驗證“尚未發(fā)射”與“已發(fā)射”兩個時間窗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 訂閱的核心特征是服務(wù)端主動、多次推送這與查詢/變更的“一次響應(yīng)一次完成”模型完全不同。MockSubscriptionLink為此提供了一套“錄制 手動觸發(fā)”的 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只有當(dāng)下游真正subscribe時才會把觀察者推入內(nèi)部observers數(shù)組并執(zhí)行所有setups監(jiān)聽器而一旦取消訂閱則執(zhí)行所有unsubscribers監(jiān)聽器。這一點非常符合 Apollo Link 的惰性求值語義——src/tests/graphqlSubscriptions.ts 的測試專門驗證了“未調(diào)用subscribe前onSetup不會被觸發(fā)”的惰性行為。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 被調(diào)用4.3 手動推送simulateResult與simulateCompletesimulateResultmockSubscriptionLink.ts模擬“服務(wù)端推來一條事件”通過setTimeout(..., result.delay || 0)支持延遲推送delay可選對當(dāng)前所有已訂閱的 observer 依次執(zhí)行result.result存在則observer.nextresult.error存在則observer.errorcomplete參數(shù)為true則observer.complete若此刻沒有任何活躍訂閱observers.length 0會拋出Error(subscription torn down)——這正是 src/tests/graphqlSubscriptions.ts 中“先client.subscribe建立流再link.simulateResult(results[0])推送、斷言發(fā)射”這一標準循環(huán)的底層機制。simulateComplete則直接對所有 observer 調(diào)complete模擬訂閱正常關(guān)閉。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();這種“測試代碼掌控推送節(jié)奏”的能力讓開發(fā)者可以精確斷言“收到第 N 條推送時 UI 處于什么狀態(tài)”也可以穿插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組合進更復(fù)雜的鏈路也可以嵌入MockLink之外的自定義鏈路中。同一倉庫的useSubscription、useSuspenseQuery、useBackgroundQuery、createQueryPreloader等大量 React hooks 測試都復(fù)用了這一組件是訂閱相關(guān)測試的事實標準工具。五、類型別名與廢棄標記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;結(jié)論很清晰新代碼請優(yōu)先使用命名空間限定形式MockLink.MockedResponse...、MockLink.Options、MockLink.ResultFunction...與MockLink.DelayFunction它們語義自包含且不會被廢棄舊的頂層別名為了兼容歷史版本仍會繼續(xù)導(dǎo)出這也是apollo/client/testing公共 API 的一部分但文檔注釋與類型層面均已提示遷移。同樣在 API 報告中CovariantUnaryFunction被標記為internal deprecated——它不參與公共導(dǎo)出只作為ResultFunction與VariableMatcher的底層構(gòu)建塊利用“取對象方法類型”的技巧把參數(shù)協(xié)變語義編碼進類型讀者無需直接使用。六、從測試用例反推使用模式快速上手的三個范式結(jié)合 src/testing/core/mocking/tests/mockLink.ts 與倉庫內(nèi)各 hooks 測試可以歸納出三種高頻使用范式范式一順序響應(yīng)隊列query/mutation 的多次調(diào)用const link new MockLink([ { request: { query: q1 }, result: { data: { a: a } } }, { request: { query: q2 }, result: { data: { b: b } } }, // 同一 query 第二次執(zhí)行 { request: { query: q1 }, result: { data: { a: a2 } } }, ]);MockLink 按“query key 變量匹配 隊列順序”精確分配天然支持多操作交替的場景。范式二錯誤注入與延遲懸掛// 注入網(wǎng)絡(luò)錯誤 { 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它們是倉庫為測試場景配套的流式斷言基礎(chǔ)設(shè)施。七、總結(jié)與進一步閱讀apollo/client/testing的 API 設(shè)計遵循了“Apollo Link 協(xié)議之上疊加可編程性”的原則MockLink用一個有序、可消耗、可延遲、可報錯的響應(yīng)隊列把服務(wù)端行為建模成確定性狀態(tài)機MockSubscriptionLink把推送權(quán)交給測試代碼realisticDelay則在兩者之間提供默認的“真實感”。其公共類型含廢棄別名由 API Extractor 統(tǒng)一維護在 .api-reports/api-report-testing.api.md源碼實現(xiàn)與測試分別位于 src/testing/core/mocking/mockLink.ts、src/testing/core/mocking/mockSubscriptionLink.ts 及其 測試目錄而 src/testing/index.ts 定義了最終導(dǎo)出的邊界。如果你想進一步探究React 側(cè)更高層的MockedProvider封裝與斷言匹配器見 src/testing/react/MockedProvider.tsx 與 src/testing/matchers/index.ts訂閱相關(guān)的端到端測試范例見 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點擊查看免費下載相關(guān)推薦Apollo Client 錯誤體系全解析apollo/client/errors 模塊 API 深度指南Apollo Client 錯誤體系全解析apollo/client/errors 模塊 API 深度指南 Apollo Client 將運行時可能遇到的錯前端GraphQLApollo Client 內(nèi)部測試工具包 apollo/client/testing/internal API 全面解析Apollo Client 內(nèi)部測試工具包 apollo/client/testing/internal API 全面解析 apollo/client/te前端GraphQLApollo Client 批量請求鏈路 BatchHttpLink 完整指南源碼級解析與實戰(zhàn)配置Apollo Client 批量請求鏈路 BatchHttpLink 完整指南源碼級解析與實戰(zhàn)配置 導(dǎo)讀 本文圍繞 Apollo Client 的 apol前端GraphQL創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考