beforeEach(() => jest.useFakeTimers());
afterEach(() => jest.useRealTimers());
test("searches 300ms after typing stops", async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
const onSearch = jest.fn();
render(<SearchBox onSearch={onSearch} />);
await user.type(screen.getByRole("searchbox"), "react");
act(() => jest.advanceTimersByTime(299));
expect(onSearch).not.toHaveBeenCalled();
act(() => jest.advanceTimersByTime(1));
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith("react");
});
Use fake timers so the test controls time, instead of waiting for real delays.
The setup:
- Call
jest.useFakeTimers(), orvi.useFakeTimers()in Vitest. - Create user-event with
userEvent.setup({ advanceTimers: jest.advanceTimersByTime }). Then user-event's own internal delays move with the fake clock.
The test:
- Type a query.
- Advance the clock to just before the 300ms delay. Assert
onSearchwas not called. - Advance the last millisecond. Assert
onSearchwas called once, with the final value. - Wrap timer advances in
actwhen they trigger React state updates. - Restore real timers in
afterEachwithjest.useRealTimers(), so other tests aren't affected.
This tests the behaviour: called once, after the pause, with the last value. It doesn't depend on how the debounce is implemented.
- Turn on fake timers with
jest.useFakeTimers()orvi.useFakeTimers(). - Use
userEvent.setup({ advanceTimers: jest.advanceTimersByTime })so user-event follows the fake clock. - Assert it was not called before the delay, and called once after it.
- Wrap timer advances that update state in
act. - Restore real timers after each test.
Read more