如何用React Testing Library的renderHook测试嵌套Context?遇result.current未定义
测试React嵌套Context时result.current为undefined的排查与解决
问题重现
我在用React Testing Library的renderHook测试嵌套Context时遇到了问题,测试代码如下:
import React, { FC, useEffect, useMemo, useState } from 'react'; import { renderHook, waitFor } from '@testing-library/react'; interface PropsCtx { input: string; myval: string; setMyVal: React.Dispatch<React.SetStateAction<string>>; } const placeholder: PropsCtx = { input: '', myval: '', setMyVal: (prev) => prev }; const CTX = React.createContext<PropsCtx>(placeholder); interface PropsProvider { input: string; children: React.ReactNode; } const MyProvider: FC<PropsProvider> = ({ children, input }) => { const [myval, setMyVal] = useState<PropsCtx['myval']>(placeholder.myval); const value = useMemo<PropsCtx>(() => ({ input, myval, setMyVal }), [input, myval, setMyVal]); return <CTX.Provider value={value}>{children}</CTX.Provider>; }; const Wrapper: FC<PropsProvider> = ({ input, children }) => ( <MyProvider input={input}>{children}</MyProvider> ); interface PropsUse { newinput: string; } const useFromProvider= ({ newinput }: PropsUse) => { const { input, myval, setMyVal } = React.useContext(CTX); useEffect(() => { setTimeout(() => { setMyVal(newinput); }, 1000) }, []); return { input, myval, setMyVal }; }; describe('useFromProvider', () => { it('should return context results', async () => { const wrapper = () => <Wrapper input={'hello'}><div>content</div></Wrapper> const { result } = renderHook( () => useFromProvider({newinput: 'goodbye'}), { wrapper } ); expect((result as any).current.input).toBeTruthy(); }); });
运行测试时出现错误:
错误
result.current === undefined
问题原因
问题出在renderHook的wrapper函数上:你当前的wrapper硬编码了<div>content</div>作为子元素,没有接收renderHook传入的children参数(也就是包装了测试hook的组件),也没有将其传递给Wrapper组件。
这导致useFromProvider根本没有被渲染在MyProvider的Context范围内,甚至可能完全没有被执行,最终result.current返回undefined。
修复方案
- 修正wrapper函数:让它接收
children参数,并将其传递给Wrapper组件,确保测试的hook被正确包裹在Context Provider中。 - 补充依赖项:给
useEffect添加缺失的newinput依赖,避免ESLint警告。 - 完善断言逻辑:利用
waitFor处理异步的setTimeout操作,确保断言在状态更新完成后执行,同时去掉any断言,利用TypeScript的类型检查。
完整修正代码
import React, { FC, useEffect, useMemo, useState } from 'react'; import { renderHook, waitFor } from '@testing-library/react'; interface PropsCtx { input: string; myval: string; setMyVal: React.Dispatch<React.SetStateAction<string>>; } const placeholder: PropsCtx = { input: '', myval: '', setMyVal: (prev) => prev }; const CTX = React.createContext<PropsCtx>(placeholder); interface PropsProvider { input: string; children: React.ReactNode; } const MyProvider: FC<PropsProvider> = ({ children, input }) => { const [myval, setMyVal] = useState<PropsCtx['myval']>(placeholder.myval); const value = useMemo<PropsCtx>(() => ({ input, myval, setMyVal }), [input, myval, setMyVal]); return <CTX.Provider value={value}>{children}</CTX.Provider>; }; const Wrapper: FC<PropsProvider> = ({ input, children }) => ( <MyProvider input={input}>{children}</MyProvider> ); interface PropsUse { newinput: string; } const useFromProvider = ({ newinput }: PropsUse) => { const { input, myval, setMyVal } = React.useContext(CTX); useEffect(() => { setTimeout(() => { setMyVal(newinput); }, 1000); }, [newinput]); // 补充依赖项 return { input, myval, setMyVal }; }; describe('useFromProvider', () => { it('should return context results and update myval asynchronously', async () => { // 修正wrapper,接收children并传递 const wrapper = ({ children }: { children: React.ReactNode }) => ( <Wrapper input={'hello'}>{children}</Wrapper> ); const { result } = renderHook( () => useFromProvider({ newinput: 'goodbye' }), { wrapper } ); // 初始状态断言 expect(result.current.input).toBe('hello'); expect(result.current.myval).toBe(''); // 等待异步更新完成 await waitFor(() => { expect(result.current.myval).toBe('goodbye'); }); }); });
内容的提问来源于stack exchange,提问作者Inspiraller
相关产品推荐
相关产品推荐

