使用Testing Library+Jest测试组件报错Cannot read property 'off' of undefined如何解决
报错原因
你遇到的TypeError: Cannot read property 'off' of undefined报错,根源是测试运行时组件依赖的外部上下文没有被注入:
组件内部通过useContext(Local.ContextCalculator)获取上下文对象,但测试代码直接渲染<Buttons />,外层没有对应Context.Provider提供值,导致useContext返回undefined,解构得到的setFlag自然也是undefined,访问setFlag.off就触发了报错。
另外组件还依赖useTranslation国际化钩子,测试环境如果没有配置i18n,也会出现翻译值异常。
解决方案
确实需要对依赖做mock或者在测试环境注入依赖,以下是两种常用实现方式:
方式1:Mock依赖(轻量快速)
直接通过jest mock钩子的返回值,不需要额外引入上下文Provider:
// 测试文件顶部添加mock逻辑 jest.mock('react', () => ({ ...jest.requireActual('react'), useContext: jest.fn() })) jest.mock('react-i18next', () => ({ useTranslation: () => ({ // 直接返回翻译key,避免依赖i18n配置 t: (key) => key }) })) describe('Buttons', () => { const {Buttons} = jest.requireActual( '../../../src/components/calculator/Buttons' ) afterEach(() => { Lib.resetAll() jest.clearAllMocks() }) it('renders as expect', async () => { // 模拟上下文返回值 let mockFlag = false const mockSetFlag = { on: jest.fn(() => { mockFlag = true }), off: jest.fn(() => { mockFlag = false }) } require('react').useContext.mockReturnValue({ flag: mockFlag, setFlag: mockSetFlag }) const view = Lib.render(<Buttons />) expect(view.asFragment()).toMatchSnapshot() Lib.fireEvent.click(Lib.screen.getByText(/buttons\.calculate/i)) expect(mockSetFlag.on).toHaveBeenCalled() await Lib.screen.findByTestId('result-table-calculator') Lib.fireEvent.click(Lib.screen.getByText(/buttons\.clear/i)) expect(mockSetFlag.off).toHaveBeenCalled() expect( Lib.screen.queryByTestId('result-table-calculator') ).not.toBeInTheDocument() }) })
方式2:外层包裹Provider(更贴近真实场景)
如果需要更贴近生产运行逻辑,可以在渲染时直接套上上下文Provider,不需要mock useContext:
// 仅需mock国际化钩子,上下文直接用真实Provider注入 jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })) describe('Buttons', () => { const {Buttons} = jest.requireActual( '../../../src/components/calculator/Buttons' ) // 替换为你项目中上下文定义的实际路径 const {Local} = require('../../../src/xxx/context-defination-path') afterEach(() => { Lib.resetAll() }) it('renders as expect', async () => { // 写个测试用的包装组件管理上下文状态 const TestWrapper = ({children}) => { const [flag, setFlagBase] = React.useState(false) const setFlag = { on: () => setFlagBase(true), off: () => setFlagBase(false) } return ( <Local.ContextCalculator.Provider value={{flag, setFlag}}> {children} </Local.ContextCalculator.Provider> ) } const view = Lib.render(<Buttons />, {wrapper: TestWrapper}) expect(view.asFragment()).toMatchSnapshot() Lib.fireEvent.click(Lib.screen.getByText(/buttons\.calculate/i)) await Lib.screen.findByTestId('result-table-calculator') Lib.fireEvent.click(Lib.screen.getByText(/buttons\.clear/i)) expect( Lib.screen.queryByTestId('result-table-calculator') ).not.toBeInTheDocument() }) })
内容的提问来源于stack exchange,提问作者Renan Bessa
相关产品推荐
相关产品推荐

