Skip to main content

How to Skip Debounce and Timer Delays

If your components use timer-based delays (debounce, polling, throttling, etc.), tests that do not care about the delay shouldn't have to wait for it. Tests must be composable โ€” changing the debounce should not break dozens of other tests.

This recipe shows you how to skip timer delays instantly using Vitest's fake timers.

We will take the example of a debounced search input โ€” the search is triggered 300ms after the user stops typing.

๐Ÿฝ๏ธ Before You Startโ€‹

๐Ÿ“„๏ธ Controlling Time in Tests

Understand why time-based behavior is challenging to test and how fake timers and dynamic timing configuration address different scenarios.

The Goalโ€‹

Let's assume that the initial test looks like this:

it(`filters recipes by author's name`, async () => {
TestBed.createComponent(CookbookSearch);

await page.getByRole('textbox', { name: 'Keywords' }).fill('Angular Testing');

/* There should be a single cookbook in the search results. */
await expect
.element(page.getByRole('heading'))
.toHaveTextContent('Angular Testing Cookbook'); // โณ Waiting at least 300ms
});

The problem with this test is that it will be as slow as the debounce delay. It could even become flaky if the debounce delay is too close to Vitest's timeout.

1. Enable Fake Timersโ€‹

To control the time, you have to replace the real timers with Vitest's fake timers using vi.useFakeTimers().

import { vi } from 'vitest';

it(`filters recipes by author's name`, async () => {
vi.useFakeTimers();

TestBed.createComponent(CookbookSearch);

await page.getByRole('textbox', { name: 'Keywords' }).fill('Angular Testing'); // โŒ This will timeout.

await expect
.element(page.getByRole('heading'))
.toHaveTextContent('Angular Testing Cookbook');
});

The test will fail with a timeout error such as TimeoutError: locator.fill: Timeout 896ms exceeded. because the default tick mode of fake timers is manual. This means that unless you manually advance time, all timers will be paused โ€” including Angular's internal scheduling.

More precisely, this prevents Angular from updating the DOM and setting the Keywords label for the input element. Therefore, the test can't find the input.

2. Turn On "Fast-Forward" Modeโ€‹

Instead of manually advancing time and coupling the test to the debounce delay, you can switch to "fast-forward" mode by calling vi.setTimerTickMode('nextTimerAsync') (available since Vitest 4.1.0).

What I call "fast-forward" mode is a tick mode for fake timers that automatically advances time on its own. Whenever you schedule a macrotask with setTimeout, for example, it will advance time by the amount of the timeout and flush the microtasks queue.

it(`filters recipes by author's name`, async () => {
vi.useFakeTimers();
vi.setTimerTickMode('nextTimerAsync');

TestBed.createComponent(CookbookSearch);

await page.getByRole('textbox', { name: 'Keywords' }).fill('Angular Testing');

await expect
.element(page.getByRole('heading'))
.toHaveTextContent('Angular Testing Cookbook');
});

This test will run as fast as if there was no debounce delay.

3. Restore the Real Timersโ€‹

To avoid affecting other tests, restore real timers after each test. Use Vitest's onTestFinished hook to keep setup and teardown colocated. This ensures that real timers are restored whether the test passes or fails, without harming readability with beforeEach and afterEach.

import { onTestFinished } from 'vitest';

describe(CookbookSearch.name, () => {
it(`filters recipes by author's name`, async () => {
setUpFastForward();

TestBed.createComponent(CookbookSearch);

await page
.getByRole('textbox', { name: 'Keywords' })
.fill('Angular Testing');

await expect
.element(page.getByRole('heading'))
.toHaveTextContent('Angular Testing Cookbook');
});
});

function setUpFastForward() {
vi.useFakeTimers().setTimerTickMode('nextTimerAsync');
onTestFinished(() => {
vi.useRealTimers();
});
}

Get the Full Pictureโ€‹

Now you know how to skip timer delays in your tests. See how this fits into a Full Pragmatic Angular Testing Strategy โ€” with hands-on exercises and live guidance.

Pragmatic Angular Testing Workshop

Source Codeโ€‹

Younes Jaaidi

~1 email per month. Unsubscribe anytime.

Pragmatic Angular Testing

๐Ÿ’ฐ 170โ‚ฌ โ†’ 80โ‚ฌ ยท Launch Price

Stop rewriting tests every time you refactor.