React中Jest测试无法追踪history.location.pathname路由变化问题
我帮你梳理下测试失败的几个核心原因,以及对应的修复步骤:
1. 路由组件嵌套错误,导致Navbar拿到的不是我们创建的history
你在测试里同时嵌套了<MemoryRouter>和<Router history={history}>,但MemoryRouter内部会自行创建专属的history实例,这就导致通过withRouter注入到Navbar的history是MemoryRouter的实例,而非你手动创建的那个,自然无法追踪到预期的路径变化。
修复:移除外层的MemoryRouter,只保留我们自定义的Router:
const testRenderer = TestRenderer.create( <Router history={history}>{componentUnderTest}</Router> );
2. 组件内routeToPage函数的判断逻辑错误
看你组件里的这段代码:
const routeToPage = documentType => { if ((documentType = "TypeOne")) { props.history.push("/app/type-one"); } else if ((documentType = "TypeTwo")) { props.history.push("/app/type-two"); } };
这里误用了赋值运算符=而非全等判断===,这会导致不管传入什么documentType,都会执行第一个分支(因为赋值表达式返回被赋的值,"TypeOne"是真值)。虽然你提到调试组件时路径能变化,但这绝对是个隐藏bug,必须修正:
const routeToPage = documentType => { if (documentType === "TypeOne") { props.history.push("/app/type-one"); } else if (documentType === "TypeTwo") { props.history.push("/app/type-two"); } };
3. 测试中未等待异步操作完成
你的handleKeyPressSearchbar是async函数,调用onKeyPress后,内部的getDocumentType和history.push都是异步执行的,但测试里直接同步调用断言逻辑,此时异步操作还未完成,所以history还没更新。
修复:用async/await配合act包裹异步操作,确保所有状态更新完成后再断言:
首先修改andInputAndEnterIsPresent为异步函数:
const andInputAndEnterIsPresent = async () => { const input = container.findByType("input"); await act(async () => { input.props.onChange({ target: { value: "Test" } }); await input.props.onKeyPress({ charCode: 13 }); }); };
然后测试函数也要改成异步:
test("should route to type-one, when documentType is TypeOne", async () => { const documentTypeOneConfig = givenDocumentTypeOne(); whenRenderingNavbar(documentTypeOneConfig); await andInputAndEnterIsPresent(); thenNavbarRoutesToTypeOne(); });
另外,你的getDocumentType模拟函数需要返回Promise(因为组件里是await调用),修正模拟逻辑:
const givenDocumentTypeOne = () => { return { getDocumentType: jest.fn(x => Promise.resolve("TypeOne")) }; };
4. 组件内搜索值的作用域问题(可选优化)
看组件里的searchfieldvalue声明:
let searchfieldvalue = null; const handleLoad = e => { searchfieldvalue = e.target.value; };
这个变量是组件函数内部的普通变量,每次组件重新渲染都会被重置为null,存在值丢失的风险。更好的做法是用React的状态管理:
const [searchfieldvalue, setSearchfieldvalue] = useState(null); const handleLoad = e => { setSearchfieldvalue(e.target.value); };
这样能确保搜索值的稳定性,也符合React的状态管理规范。
最终修正后的核心测试代码
test("should route to type-one, when documentType is TypeOne", async () => { const documentTypeOneConfig = givenDocumentTypeOne(); whenRenderingNavbar(documentTypeOneConfig); await andInputAndEnterIsPresent(); thenNavbarRoutesToTypeOne(); }); const givenDocumentTypeOne = () => { return { getDocumentType: jest.fn(x => Promise.resolve("TypeOne")) }; }; const whenRenderingNavbar = testValues => { const testContextValues = { ...defaultContextValues, ...testValues }; jest .spyOn(SomeContext, "useSomeContext") .mockImplementation(() => testContextValues); const testRenderer = TestRenderer.create( <Router history={history}>{componentUnderTest}</Router> ); container = testRenderer.root; }; const andInputAndEnterIsPresent = async () => { const input = container.findByType("input"); await act(async () => { input.props.onChange({ target: { value: "Test" } }); await input.props.onKeyPress({ charCode: 13 }); }); };
把这些修改点落实后,你的测试应该就能正确追踪到history.location.pathname的变化了。
内容的提问来源于stack exchange,提问作者bastian

