How to test a call of a function that's inside a setTimeout?

Viewed 47

Hello I'm trying to test a call of a function that is inside a setTimeout. This is my code:

const CreateContact = (): JSX.Element => {
  const navigate = useNavigate();
  const { createContact } = useContactsApi();
  
  const [formData, setFormData] = useState(formDataInitialState);

  const handleSubmit = async (event: SyntheticEvent) => {
    event.preventDefault();
    
      try {
        await createContact(formData);
        
        setTimeout(() => {
          navigate("/home");
        }, 2500);
      } catch (error) {}
  };
  
  // More code...
};

I have made many tries and I can't pass the coverage on the line where is called navigate("/home");.

Here is my last test:

const navigate = jest.fn();
beforeEach(() => {
  jest.spyOn(router, "useNavigate").mockImplementation(() => navigate);
});

test("Then it should call the navigate function", async () => {
      const newText = "test@prove";

      render(
        <MemoryRouter>
          <Provider store={store}>
            <CreateContact />
          </Provider>
        </MemoryRouter>
      );

      const form = {
        name: screen.getByLabelText("Name") as HTMLInputElement,
      };
          
      await userEvent.type(form.name, newText);

      const submit = screen.getByRole("button", { name: "Create contact" });
      await userEvent.click(submit);

      jest.runAllTimers();

      setTimeout(() => {
        expect(navigate).toHaveBeenCalledTimes(1);
      }, 4000);
    });

The test runs ok, but the coverage doesn't pass inside the setTimeout.

1 Answers

First i would mock the useNavigate hook in your test file.

const navigateMock = jest.fn();

jest.mock('react-router-dom', () => ({
   ...jest.requireActual('react-router-dom'),
   useNavigate: () => navigateMock
}));

Then in your test i would simply check if navigateMock is called, without using any setTimout, since you call jest.runAllTimers()

test("Then it should call the navigate function", async () => {
  const newText = "test@prove";

  render(
    <MemoryRouter>
      <Provider store={store}>
        <CreateContact />
      </Provider>
    </MemoryRouter>
  );

  const form = {
    name: screen.getByLabelText("Name") as HTMLInputElement,
  };
  
  await userEvent.type(form.name, newText);

  const submit = screen.getByRole("button", { name: "Create contact" });
  await userEvent.click(submit);

  jest.runAllTimers();

  expect(navigateMock).toHaveBeenCalledTimes(1);
});
Related