Search code examples
javascriptreactjsjestjsreact-testing-library

Can't update the searched todos in the react testing library?


The program is working fine but when I try to test it does not work properly. I want to rerender the todos after they are searched but this part in the test seems to not work properly: I fetch the data and set it to the todos, also the search state is working because when I log it I can see the value, however, the rerender is not triggered and the searched items are not displayed, I can see still all the todos. I don't know how to do it. What should I do? here is the code:

useEffect(() => {
    fetch("some url todos")
      .then((response) => {
        return response.json();
      })
      .then((response) => { 
        setTodos((todos) => ({ ...todos, all: response }));
      })
      .catch((e) => console.error(e)); 
  }, []);

  useEffect(() => { 
    setTodos((todos) => ({
      ...todos,
      searched: search
        ? todos.all.filter((item) => { return item.title.toLowerCase().includes(search.toLowerCase());
          })
        : null,
    }));
  }, [search]);

  const handleOnChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(e.target.value);
  };
 <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>

Here are codes for the test:

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);
  });

Solution

  • because you are rendering component and setting the state during rendering, you need to wait for the ui to be fully rendered using the waitFor async utitliy function.

    a good technique that works for me. adding waitFor fun after the render.

    render(
      <MemoryRouter>
        <Home />
      </MemoryRouter>
    );
    
    await waitFor(async () => Promise<void>);
    

    and the rest should works fine.