如何在测试中仅监听指定的React Hook useState(如setMovies)
解决思路
你现在的实现给所有useState返回了同一个setter mock,所以所有状态更新都会触发相同的日志。要隔离监听setMovies,只需要在mock useState的时候,给目标useState返回专属的监听setter,其他useState走普通逻辑即可。
方案1:通过初始值匹配(最稳妥,无需修改业务代码)
如果提前知道setMovies对应的状态初始值(比如电影列表初始值是空数组[]),直接在mock实现里判断传入的初始值,匹配到就返回带监听的setter,其余useState返回原生实现或者普通mock:
import React, { Dispatch, SetStateAction } from 'react'; // 入参传入目标useState的初始值,用来匹配 const createMockUseState = <T extends {}>(targetInitialValue: T) => { type TSetState = Dispatch<SetStateAction<T>>; // 专属监听setter,只给setMovies用 const targetSetState: TSetState = jest.fn((prop) => { console.log('jest - spy setMovies 调用 = ', prop); }); // 其他useState用的普通setter const defaultSetState: TSetState = jest.fn(); const spyUseState = jest.spyOn(React, 'useState') as jest.SpyInstance; spyUseState.mockImplementation((initialValue) => { // 匹配到目标初始值,返回监听setter if (initialValue === targetInitialValue) { return [initialValue, targetSetState]; } // 其余走普通mock,也可以换成 jest.requireActual('react').useState 走原生实现 return [initialValue, defaultSetState]; }); // 把targetSetState暴露出去,方便后续断言调用次数/参数 return { targetSetState }; }; interface Props { propertyToTest: boolean }; describe('Search Movies', () => { describe('Onload - do first search()', () => { beforeAll(async () => { // 传入setMovies对应的初始值,比如是空数组就传[] const { targetSetState } = createMockUseState([]); wrapper = mount( <ProviderMovies> <SearchMovies /> </ProviderMovies> ); await new Promise((resolve) => setImmediate(resolve)); await act( () => new Promise<void>((resolve) => { resolve(); }) ); // 后续可以直接断言 expect(targetSetState).toHaveBeenCalledTimes(1); }); }); });
方案2:通过调用顺序匹配
如果多个useState初始值相同,你可以提前数好setMovies对应的useState是组件渲染时第几次调用的,按次数匹配即可:
const createMockUseState = <T extends {}>(targetCallIndex: number) => { type TSetState = Dispatch<SetStateAction<T>>; const targetSetState: TSetState = jest.fn((prop) => { console.log('jest - spy setMovies 调用 = ', prop); }); const defaultSetState: TSetState = jest.fn(); // 计数useState调用次数 let callCount = 0; const spyUseState = jest.spyOn(React, 'useState') as jest.SpyInstance; spyUseState.mockImplementation((initialValue) => { callCount++; // 调用次数匹配目标就返回监听setter if (callCount === targetCallIndex) { return [initialValue, targetSetState]; } return [initialValue, defaultSetState]; }); return { targetSetState }; }; // 使用的时候传入setMovies是第几次调用的useState,比如是第2次就传2 const { targetSetState } = createMockUseState(2);
注意事项
- 方案2依赖组件内useState的调用顺序,后续如果修改了组件内useState的顺序,需要同步更新测试里的序号。
- 如果不需要mock其他useState的逻辑,可以把普通返回的部分换成原生useState实现,性能更好:
const originalUseState = jest.requireActual('react').useState; // 不匹配的情况直接返回原生useState return originalUseState(initialValue);
内容的提问来源于stack exchange,提问作者Inspiraller
相关产品推荐
相关产品推荐

