同一测试中切换API响应测试组件状态的问题(useSWR+Jest)
问题描述
我要测试一个组件,它先显示加载界面,等API返回数据后再展示对应内容。我想在同一个测试用例里完成状态切换测试(不想拆分到不同测试套件),因为API可能随时间返回更多数据。我用了useSWR,但测试时Mock了自定义hook useCustomHook,步骤如下:
- 导入并Mock该hook
- 渲染组件,断言加载状态显示
- 修改Mock hook的响应内容
- 但第二次断言不通过,而且Mock hook没被再次调用
测试代码如下:
import { useCustomHook } from "./useCustomHook" jest.mock( "useCustomHook", () => ({ useCustomHook: jest.fn(() => ({ isLoading: true, response: undefined, })), }), ) const mockuseCustomHook = useCustomHook as jest.Mock describe("Component", () => { it('Should render properly', () => { render(<Component />) expect(screen.getByText('Loading')).toBeInTheDocument() mockuseCustomHook.mockImplementationOnce(() => ({ isLoading: false, response: { data: 'This is the data' }, })) expect(screen.queryByText('Loading')).not.toBeInTheDocument() expect(screen.getByText('This is the data')).toBeInTheDocument() }) })
问题原因
核心问题是修改Mock实现后,组件没有触发重新渲染。React组件只会在props变化、内部state更新或依赖项变更时重新渲染,你只是修改了Mock hook的返回逻辑,但组件已经完成初始渲染,hook不会自动重新执行,自然不会更新界面状态。
解决方案
方案1:用rerender触发组件更新(推荐)
React Testing Library的render函数会返回rerender方法,可高效触发组件重新渲染,复用已有的DOM结构:
describe("Component", () => { it('Should render properly', () => { // 保存rerender方法 const { rerender } = render(<Component />) // 验证初始加载状态 expect(screen.getByText('Loading')).toBeInTheDocument() // 更新Mock的持久化实现(不要用mockImplementationOnce,避免单次调用限制) mockuseCustomHook.mockImplementation(() => ({ isLoading: false, response: { data: 'This is the data' }, })) // 触发组件重新渲染,读取新的Mock返回值 rerender(<Component />) // 验证数据展示状态 expect(screen.queryByText('Loading')).not.toBeInTheDocument() expect(screen.getByText('This is the data')).toBeInTheDocument() }) })
方案2:重新调用render
如果不需要复用DOM结构,也可以直接再次调用render重新渲染组件:
describe("Component", () => { it('Should render properly', () => { // 初始渲染,验证加载状态 render(<Component />) expect(screen.getByText('Loading')).toBeInTheDocument() // 更新Mock实现 mockuseCustomHook.mockImplementation(() => ({ isLoading: false, response: { data: 'This is the data' }, })) // 重新渲染组件 render(<Component />) // 验证数据展示状态 expect(screen.queryByText('Loading')).not.toBeInTheDocument() expect(screen.getByText('This is the data')).toBeInTheDocument() }) })
注意事项
- 避免使用
mockImplementationOnce:初始渲染已经消耗了一次Mock调用,后续重新渲染需要持久化的Mock逻辑,改用mockImplementation即可。 - 如果
useCustomHook接收参数,可在mockImplementation中添加条件判断,匹配不同参数返回对应结果。
内容的提问来源于stack exchange,提问作者Angelo Dias
相关产品推荐
相关产品推荐

