You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:22:45