React Testing Library中搜索后的Todos无法更新问题
React Testing Library测试Todos搜索功能异常
项目功能运行正常,但在React Testing Library中测试Todos搜索功能时出现异常:已成功获取数据并设置到Todos,search状态值可正常打印,但组件未触发重渲染,仍显示全部Todos,无法展示搜索结果。
组件相关代码
useEffect(() => { fetch("some url todos") .then((response) => response.json()) .then((response) => { setTodos((todos) => ({ ...todos, all: response })); }) .catch((e) => console.error(e)); }, []); useEffect(() => { setTodos((todos) => ({ ...todos, searched: search ? todos.all.filter((item) => item.title.toLowerCase().includes(search.toLowerCase())) : null, })); }, [search]); const handleOnChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => { setSearch(e.target.value); }; return ( <> <div className="search-container"> <input className="search" value={search} onChange={handleOnChangeInput} placeholder="Search todo..." data-testid="search" type="text" /> </div> <div className="todos" data-testid="todos"> {(todos.searched && todos.searched.length > 0 ? todos.searched : todos.all ).map(({title}) => ( <p data-testid="todo"> {title} </p> ))} </div> </> )
测试代码
const mockResponse = [ { userId: 1, id: 1, title: "Todo S", completed: false, }, { userId: 1, id: 2, title: "Todo A", completed: true, }, ]; beforeEach(() => { jest.spyOn(global, "fetch" as any).mockResolvedValue({ json: () => mockResponse, }); }); afterEach(() => { jest.restoreAllMocks(); }); it("should filter todos based on search input", async () => { render( <MemoryRouter> <Home /> </MemoryRouter> ); const searchInput = screen.getByTestId("search"); fireEvent.change(searchInput, { target: { value: "A" }, }); const todos = await screen.findAllByTestId("todo"); expect(todos).toHaveLength(1); });
问题原因
测试中修改搜索值时,todos.all还未被异步请求填充。组件里依赖search的useEffect会在search变化时立即执行,但此时fetch的异步请求还未完成,todos.all仍是初始状态(比如undefined),过滤逻辑无法生成正确的搜索结果。后续fetch完成后,虽然todos.all更新了,但search没有再次变化,所以这个useEffect不会重新执行,组件也就不会展示过滤后的结果。
解决方案
1. 测试中等待数据加载完成
在操作搜索框前,先等待异步数据渲染完成,确保todos.all已经被填充:
it("should filter todos based on search input", async () => { render( <MemoryRouter> <Home /> </MemoryRouter> ); // 等待初始todo加载完成,确保todos.all已填充 await screen.findAllByTestId("todo"); const searchInput = screen.getByTestId("search"); fireEvent.change(searchInput, { target: { value: "A" }, }); // 等待过滤后的结果渲染完成 const filteredTodos = await screen.findAllByTestId("todo"); expect(filteredTodos).toHaveLength(1); });
2. 优化组件过滤逻辑(推荐)
去掉依赖search的useEffect,直接在渲染时计算过滤结果,这样状态变化时会自动重新计算,避免依赖副作用的时机问题:
// 移除原有的依赖search的useEffect,直接在渲染时计算过滤结果 const filteredTodos = search ? todos.all?.filter(item => item.title.toLowerCase().includes(search.toLowerCase())) : null; return ( <> <div className="search-container"> <input className="search" value={search} onChange={handleOnChangeInput} placeholder="Search todo..." data-testid="search" type="text" /> </div> <div className="todos" data-testid="todos"> {(filteredTodos && filteredTodos.length > 0 ? filteredTodos : todos.all )?.map(({title, id}) => ( <p key={id} // 添加唯一key,符合React列表渲染要求 data-testid="todo" > {title} </p> ))} </div> </> )
内容的提问来源于stack exchange,提问作者FaFa
相关产品推荐
相关产品推荐

