React测试Apollo useSubscription Hook遇阻,寻求解决方案
我明白你在测试Apollo的useSubscription时遇到的困扰——加载状态测试能过,但验证数据返回时就报错说没有更多模拟响应。结合你的代码和报错信息,我来帮你梳理问题原因和解决方案:
问题核心原因
@apollo/react-testing的MockedProvider默认对每个模拟请求只提供一次响应。而你的场景中,组件可能因为重渲染(比如内部组件定义导致的重复挂载、useEffect重复触发mutation),导致订阅被多次发起,但你的mock只配置了一次响应,第二次请求就会触发"No more mocked responses"错误。
解决方案分步走
1. 给订阅Mock添加multiple: true
这是最直接解决报错的办法,告诉MockedProvider这个订阅的mock可以被重复使用:
// 修改你的mocks数组中订阅的配置 { request: { query: GET_RUNNING_DATA_SUBSCRIPTION, }, result: { data: { onLastPowerUpdate: { result1: 'string', result2: 'string' }, }, }, multiple: true, // 新增这个属性,允许重复响应 }
2. 优化内部组件避免不必要的重订阅
你的SubscriptionData是定义在Dashboard内部的函数组件,每次Dashboard渲染时都会重新创建这个组件,导致useSubscription被反复执行、订阅被重复发起。把它提取到外部或者用React.memo包裹:
// 移到Dashboard组件外部,或用React.memo包裹 const SubscriptionData = React.memo(() => { const { data, loading } = useSubscription(GET_RUNNING_DATA_SUBSCRIPTION); if (loading) { return <Heading>Data loading...</Heading>; } const metrics = []; if (data) { console.log('DATA NOW CALLED IN TEST!'); // 这里可以添加数据处理逻辑 } return metrics; }); const Dashboard: React.FC<RouteComponentProps & Props> = props => { // ... 原组件逻辑不变 };
3. 稳定useEffect的依赖项减少重复触发
你的useEffect依赖了startGetRunningData和stopGetRunningData——这两个是useMutation返回的函数,每次组件渲染都会生成新的引用,导致useEffect反复执行,间接触发订阅重连。用useCallback包裹让函数引用稳定:
const Dashboard: React.FC<RouteComponentProps & Props> = props => { const userHasProduct = !!props.user.serialNumber; const [startGetRunningData] = useMutation(START_GET_RUNNING_DATA); const [stopGetRunningData] = useMutation(STOP_GET_RUNNING_DATA); // 包裹mutation调用函数,确保引用稳定 const memoizedStart = useCallback(() => { startGetRunningData({ variables: { serialNumber: props.user.serialNumber } }); }, [startGetRunningData, props.user.serialNumber]); const memoizedStop = useCallback(() => { stopGetRunningData(); }, [stopGetRunningData]); useEffect(() => { memoizedStart(); return () => { memoizedStop(); }; }, [memoizedStart, memoizedStop]); // ... 原组件逻辑不变 };
4. 完善测试中的异步等待
在测试中确保等待订阅数据更新完成,再做断言:
it('should render subscription data', async () => { const component = create( <Provider store={store}> <MockedProvider mocks={mocks} addTypename={false}> <Router history={fakeHistory}> <Dashboard /> </Router> </MockedProvider> </Provider>, ); await wait(0); // 等待异步更新完成 // 这里可以添加断言,比如检查加载状态消失,或者数据是否渲染 expect(component.root.queryByType(Heading)).toBeNull(); });
总结
先尝试给订阅mock加上multiple: true解决报错,再通过组件优化减少不必要的重渲染和订阅发起,最后完善测试中的异步等待逻辑,应该就能顺利测试useSubscription的数据返回状态了。
内容的提问来源于stack exchange,提问作者Harry Lincoln

