Apollo MockedProvider下useLazyQuery的onCompleted未触发测试问题咨询
问题原因与解决方法
你遇到的onCompleted不触发的问题,在Apollo Client 3.3.x版本配合MockedProvider测试时非常常见,可按照以下步骤逐一排查修复:
1. 先修复测试代码的路由配置错误
你当前的路由配置存在两处明显问题,会导致组件无法拿到正确的productId,最终请求参数和mock不匹配,无法触发成功回调:
- 你配置的
MemoryRouter初始路径是contracts/311,但匹配组件的路由是products/:productId,路径完全不匹配,组件拿不到正确的商品ID参数 - 内层已经使用
MemoryRouter,外层render配置又加了wrapper: BrowserRouter,两层路由会导致路径解析冲突
修改后的render函数参考:
const renderComponent = (mocksData) => { render( <MockedProvider mocks={mocksData} defaultOptions={{ query: { fetchPolicy: "no-cache" }, watchQuery: { fetchPolicy: 'no-cache' }, }} addTypename={false} > <ServicesContextProvider freightRateServices={{}}> <ContractInformationPageProvider> <MemoryRouter initialEntries={["/products/311"]}> <Route path="products/:productId"> <ContractInformationPage /> </Route> </MemoryRouter> </ContractInformationPageProvider> </ServicesContextProvider> </MockedProvider> // 删掉外层BrowserRouter wrapper ); };
2. 检查mock数据的匹配性
MockedProvider会严格匹配请求的query和变量,只要有一处不一致就不会返回成功结果,自然不会触发onCompleted:
- 确保mock中使用的
GET_DATA和组件内引入的是同一个导出对象,不要手动重写query结构 - 确保mock的
request.variables和组件实际请求的变量完全一致,包括数据类型,比如id如果是数字311,mock中就不能写字符串"311",first、page、isBase三个字段也要完全匹配 - 可以在mock的result函数中加日志验证是否匹配成功,示例:
const testMock = [ { request: { query: GET_DATA, variables: { id: 311, first: 999999, page: 1, isBase: true } }, result: () => { console.log('mock匹配成功') return { data: { dataWithFilters: { data: {/* 你的测试返回数据 */} } } } } } ]
如果测试时没有打印上述日志,说明mock匹配失败,优先排查变量和query的一致性。
3. 测试时等待异步操作完成
MockedProvider的响应是异步的,不能直接用同步查询方法断言,必须等待请求返回:
- 优先用
findBy*系列异步查询方法,等待onCompleted执行后渲染的内容出现 - 如果需要验证状态变化,用
waitFor包裹断言逻辑,示例:
test('验证onCompleted触发后数据正常渲染', async () => { renderComponent(testMock) // 等待onCompleted执行后渲染的目标内容,比如某个数据字段 const dataDom = await screen.findByText('预期渲染的测试数据') expect(dataDom).toBeInTheDocument() })
4. 修复useLazyQuery的配置写法(针对3.3.16版本已知bug)
Apollo Client 3.3.16版本存在配置合并bug:如果在useLazyQuery定义时传了onCompleted,调用loadData时又传了其他配置,会覆盖掉定义时的回调,导致onCompleted丢失。建议二选一使用以下写法:
写法1:所有配置放在useLazyQuery定义中,调用时不传参数
const [loadData, { loading }] = useLazyQuery( GET_DATA, { variables: { id: productId, first: 999999, page: 1, isBase: true }, onCompleted: handleData, fetchPolicy: "network-only" } ); useEffect(() => { loadData(); // 不再重复传配置 }, [loadData, productId]);
写法2:所有配置放在loadData调用时传入
const [loadData, { loading }] = useLazyQuery(GET_DATA); useEffect(() => { loadData({ variables: { id: productId, first: 999999, page: 1, isBase: true }, onCompleted: handleData, fetchPolicy: "network-only" }); }, [loadData, productId]);
内容的提问来源于stack exchange,提问作者Zichen Ma
相关产品推荐
相关产品推荐

