React组件Jest测试:Mock Store、函数及设置初始State方法
React组件Jest测试Mock问题
组件代码(ParentComponent.js)
import React, {useEffect, useState} from 'react' import store from '../store_path' import loadable from '@loadable/component' import useTokenHook from 'hook_path' const ChildComponent = loadable(() => import('../childComponent_path')) export const ParentComponent = () => { const token = useTokenHook() const [data, setData] = useState({}) const [showChild, setShowChild] = useState(false) const lang = store(state => state.lang) const apiCall = store(state => state.apicall) const myFn = async() => { const res = await apiCall(); setData(res) setShowChild(true) } useEffect(() => { myFn() }, []) return ( <> {showChild ? <ChildComponent data={data} /> : 'No data found' } </> ) }
需求
为该组件编写Jest测试用例,需要实现:
- Mock
store,设置lang默认值为'en' - 让
apiCall返回指定值 - 在测试中将
showChild的初始值设为true
尝试过的方法及报错
方法1
jest.mock('../store_path', () => ({ lang: 'en', apiCall: jest.fn(() => { return someValue }) }))
报错:
TypeError: (0, _store.default) is not a function
方法2
const appStore = jest.mock('../store_path', () => jest.fn()) appStore.mockImplementation(() => ({ lang: 'en', apiCall: jest.fn(() => { return someValue }) }))
报错:
appStore.mockImplementation is not a function
解决方案
1. 正确Mock Store
你代码里的store是个函数(因为用了store(state => state.lang)这种调用方式),所以mock时不能直接返回对象,得返回一个能接收选择器参数的函数,根据选择器返回对应值:
// 定义要mock的store状态 const mockStoreState = { lang: 'en', apicall: jest.fn().mockResolvedValue('你要指定的返回值') // 设置apiCall的返回值 } // Mock store模块 jest.mock('../store_path', () => { // 返回模拟的store函数,传入选择器后返回对应状态值 return jest.fn((selector) => selector(mockStoreState)) })
这样组件调用store(state => state.lang)时会拿到'en',调用store(state => state.apicall)时会拿到预先mock好的函数。
2. 将showChild初始值设为true
给你两种简单实现方式:
方式一:直接Mock useState指定初始值
通过拦截useState调用,修改对应状态的初始值:
import React from 'react' import { render } from '@testing-library/react' import { ParentComponent } from './ParentComponent' // 拦截useState,判断初始值为false时返回true jest.spyOn(React, 'useState').mockImplementation((initialValue) => { if (initialValue === false) { return [true, jest.fn()] // 第一个值是状态,第二个是setter(用jest.fn()占位即可) } // 其他useState调用保留原初始值 return [initialValue, jest.fn()] }) test('showChild初始为true时渲染子组件', () => { const { getByTestId } = render(<ParentComponent />) // 假设子组件添加了data-testid="child-component",验证它存在 expect(getByTestId('child-component')).toBeInTheDocument() })
方式二:等待组件自然触发状态变化
如果不需要强制初始值,只是验证api调用后showChild变为true的状态,直接等待useEffect执行完成即可:
import { render, waitFor } from '@testing-library/react' import { ParentComponent } from './ParentComponent' test('api调用完成后渲染子组件', async () => { const { getByText, getByTestId } = render(<ParentComponent />) // 初始状态显示"No data found" expect(getByText('No data found')).toBeInTheDocument() // 等待api调用完成,状态更新 await waitFor(() => { expect(getByTestId('child-component')).toBeInTheDocument() }) })
完整测试示例
import React from 'react' import { render, waitFor } from '@testing-library/react' import { ParentComponent } from './ParentComponent' // Mock store const mockApiCall = jest.fn().mockResolvedValue({ name: '测试数据' }) jest.mock('../store_path', () => jest.fn((selector) => { return selector({ lang: 'en', apicall: mockApiCall }) })) // Mock异步加载的子组件(避免测试中的异步问题) jest.mock('@loadable/component', () => (loadFn) => { const Component = loadFn().default return Component }) // 或者直接Mock子组件 jest.mock('../childComponent_path', () => () => <div data-testid="child-component" />) test('组件正确渲染并处理api返回', async () => { // Mock useState让showChild初始为true jest.spyOn(React, 'useState').mockImplementation((init) => { if (init === false) return [true, jest.fn()] return [init, jest.fn()] }) const { getByTestId } = render(<ParentComponent />) // 验证子组件渲染 expect(getByTestId('child-component')).toBeInTheDocument() // 验证apiCall被调用 expect(mockApiCall).toHaveBeenCalled() })
内容的提问来源于stack exchange,提问作者Urvesh Nagda
相关产品推荐
相关产品推荐

