如何为使用readQuery/writeQuery的ApolloClient缓存更新编写单元测试
如何测试Apollo Client中useMutation的自定义update缓存逻辑
优先选择直接测试customUpdateFunction的方案:该函数本身和React上下文完全解耦,仅依赖传入的缓存实例与mutation返回数据,不需要渲染组件就能覆盖所有逻辑分支,测试运行速度快、问题定位精准。如果需要验证组件和hook的完整联动,再补充组件层面的集成测试即可。
方案1:直接对customUpdateFunction做单元测试(推荐)
测试核心思路是用Apollo官方提供的真实InMemoryCache实例作为入参,不要手动mock缓存读写方法,才能真实复现readQuery/writeQuery的实际行为,覆盖两个核心场景:
- 缓存中不存在
LIST_QUERY对应数据时,函数直接返回,不执行写入操作、不抛错 - 缓存中存在
LIST_QUERY对应数据时,函数正确执行不可变更新,移除目标条目后写回缓存
参考测试代码:
import { InMemoryCache } from '@apollo/client'; import { LIST_QUERY } from './your-gql-defs'; // 注意要把customUpdateFunction从组件文件中导出,方便测试引入 import { customUpdateFunction } from './ExampleComponent'; describe('customUpdateFunction', () => { let testCache; // 测试用初始列表数据 const mockInitialList = { itemList: [ { __typename: 'Item', id: 'i1', content: '测试条目1' }, { __typename: 'Item', id: 'i2', content: '测试条目2' } ] }; // 模拟删除i1后的mutation返回结果 const mockMutationResult = { data: { removeItem: { __typename: 'Item', id: 'i1' } } }; beforeEach(() => { // 每个用例初始化全新缓存,避免状态交叉污染 testCache = new InMemoryCache(); // 给缓存方法加spy,验证调用逻辑是否符合预期 jest.spyOn(testCache, 'readQuery'); jest.spyOn(testCache, 'writeQuery'); }); test('缓存无目标查询数据时直接退出,不执行写入', () => { customUpdateFunction(testCache, mockMutationResult); expect(testCache.readQuery).toHaveBeenCalledWith({ query: LIST_QUERY }); expect(testCache.writeQuery).not.toHaveBeenCalled(); }); test('缓存存在目标查询数据时,正确移除对应条目后写回缓存', () => { // 预先写入初始列表数据到缓存 testCache.writeQuery({ query: LIST_QUERY, data: mockInitialList }); customUpdateFunction(testCache, mockMutationResult); const updatedCacheData = testCache.readQuery({ query: LIST_QUERY }); // 验证更新结果 expect(updatedCacheData.itemList).toHaveLength(1); expect(updatedCacheData.itemList[0].id).toBe('i2'); expect(testCache.writeQuery).toHaveBeenCalledTimes(1); }); });
方案2:组件层面集成测试(补充验证)
如果需要验证useMutation钩子和update逻辑在组件实际运行时的完整联动,可以用React测试库配合Apollo官方提供的MockedProvider做集成测试:
- 配置mock数据:分别给
LIST_QUERY、REMOVE_ITEM_MUTATION配置对应的mock返回 - 渲染组件后触发删除操作
- 通过页面渲染的内容、或者从
MockedProvider实例中取出缓存对象,验证更新结果符合预期
参考测试代码:
import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; import { LIST_QUERY, REMOVE_ITEM_MUTATION, VARIABLES, ExampleComponent } from './ExampleComponent'; // 带初始缓存数据的mock配置 const testMocks = [ { request: { query: LIST_QUERY }, result: { data: { itemList: [ { __typename: 'Item', id: 'i1', content: '测试条目1' }, { __typename: 'Item', id: 'i2', content: '测试条目2' } ] } } }, { request: { query: REMOVE_ITEM_MUTATION, variables: VARIABLES }, result: { data: { removeItem: { __typename: 'Item', id: 'i1' } } } } ]; test('触发删除操作后,页面列表正确移除目标条目', async () => { render( <MockedProvider mocks={testMocks} addTypename={true}> <ExampleComponent /> </MockedProvider> ); // 触发删除动作,比如点击对应删除按钮 fireEvent.click(screen.getByRole('button', { name: '删除测试条目1' })); await waitFor(() => { expect(screen.queryByText('测试条目1')).not.toBeInTheDocument(); expect(screen.getByText('测试条目2')).toBeInTheDocument(); }); });
注意:所有测试场景都不要手动mock
readQuery/writeQuery的内部实现,必须使用真实的InMemoryCache实例,否则可能出现mock行为和Apollo实际缓存逻辑不一致,导致测试通过但线上功能异常的问题。
内容的提问来源于stack exchange,提问作者jmancherje
相关产品推荐
相关产品推荐

