Jest测试日期函数时出现Invalid hook call错误如何解决
问题原因
Invalid hook call报错的核心原因是违反了React Hook调用规则:
findNextClosestDate是普通JavaScript工具函数,既不是React函数组件,也不是自定义Hook- 你在这个普通函数内部直接调用了
useAppSelector这个React Hook,而Hook仅允许在React函数组件顶层、自定义Hook顶层调用,普通函数直接调用Hook无论在业务运行环境还是Jest直接调用的测试环境,都会触发该报错。
修复方案
优先选择第一种方案,保持工具函数纯函数特性,易维护易测试。
方案1:将依赖的可变值作为入参传入(推荐)
把原本在函数内部通过Hook获取的language改为函数入参,移除函数内部的Hook调用,让findNextClosestDate成为纯函数。
修改后的工具函数代码:
export function findNextClosestDate( array: string[] | undefined, target: Date, language: string // 新增语言入参 ): string | undefined { const targetDate = target.getTime(); if (!array) return undefined; const closestDateFromArray = array.reduce( (accumulator: string | undefined, currentValue: string) => { const currentValueTime = new Date(currentValue).getTime(); const accumulatedTime = accumulator && new Date(accumulator).getTime(); if ( currentValueTime > targetDate && (!accumulatedTime || currentValueTime < accumulatedTime) ) { return currentValue; } return accumulator; }, undefined ); return closestDateFromArray ? new Date(closestDateFromArray).toLocaleString(language, { month: "long", day: "numeric", }) : undefined; }
对应修改测试用例,调用时传入测试环境指定的语言参数即可:
import { findNextClosestDate } from "./findNextClosestDate"; describe("findNextClosestDate", () => { it("return correct closest next date", () => { const array = [ "2022-06-20 08:40:00", "2022-06-01 11:08:00", "2022-06-08 10:48:00", "2022-05-03 08:03:00", "2022-05-01 09:58:00", "2022-06-19 08:41:00", ]; const target = new Date("2022-05-24 12:04:00"); const expected = "June 1"; // 传入en-US匹配英文日期格式化结果 expect(findNextClosestDate(array, target, "en-US")).toEqual(expected); }); });
业务组件中调用该工具函数时,在组件层调用Hook拿到语言值再传入即可:
const YourComponent = () => { const language = useAppSelector(selectLocalizationLanguage); // 其余业务逻辑 const nextDate = findNextClosestDate(dateList, targetDate, language); return <span>{nextDate}</span> }
方案2:改造为自定义Hook(不推荐)
如果一定要在函数内部获取Redux状态,可将函数重命名为use开头的自定义Hook,测试时需要配合React测试库、Redux Provider包裹进行测试,这种写法会让工具函数和React生态强耦合,测试成本更高。
改造后的Hook代码:
import { selectLocalizationLanguage } from "domain/localization/localizationSlice"; import { useAppSelector } from "domain/storeHooks"; // 函数名以use开头,符合自定义Hook命名规范 export function useFindNextClosestDate( array: string[] | undefined, target: Date ): string | undefined { const language = useAppSelector(selectLocalizationLanguage); const targetDate = target.getTime(); if (!array) return undefined; const closestDateFromArray = array.reduce( (accumulator: string | undefined, currentValue: string) => { const currentValueTime = new Date(currentValue).getTime(); const accumulatedTime = accumulator && new Date(accumulator).getTime(); if ( currentValueTime > targetDate && (!accumulatedTime || currentValueTime < accumulatedTime) ) { return currentValue; } return accumulator; }, undefined ); return closestDateFromArray ? new Date(closestDateFromArray).toLocaleString(`${language}`, { month: "long", day: "numeric", }) : undefined; }
对应测试用例需要用renderHook配合Store包装器:
import { renderHook } from "@testing-library/react"; import { Provider } from "react-redux"; import store from "domain/store"; import { useFindNextClosestDate } from "./findNextClosestDate"; describe("findNextClosestDate", () => { it("return correct closest next date", () => { const array = [ "2022-06-20 08:40:00", "2022-06-01 11:08:00", "2022-06-08 10:48:00", "2022-05-03 08:03:00", "2022-05-01 09:58:00", "2022-06-19 08:41:00", ]; const target = new Date("2022-05-24 12:04:00"); const expected = "June 1"; const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>; const { result } = renderHook(() => useFindNextClosestDate(array, target), { wrapper }); expect(result.current).toEqual(expected); }); });
内容的提问来源于stack exchange,提问作者Seweryn Kuliga
相关产品推荐
相关产品推荐

