DEV Community

Cover image for Testando debounce e throttle no React com Jest [setTimeout]
Vitor Rios
Vitor Rios

Posted on

Testando debounce e throttle no React com Jest [setTimeout]

Implementar debounce ou throttle no React é comum em buscas, inputs e eventos de scroll. Mas testar esse comportamento pode parecer desafiador à primeira vista — especialmente por envolver tempo, timers assíncronos e side effects.

Neste artigo, você vai aprender:

  • Diferença entre debounce e throttle
  • Como implementá-los no React
  • Como testá-los com Jest e @testing-library/react
  • Como usar jest.useFakeTimers() do jeito certo

🔍 O que é debounce e throttle?

🐢 debounce

Adia a execução até X ms após a última chamada.

🧠 Ideal para: buscas ao digitar, auto-saves, etc.

🐇 throttle

Executa no máximo uma vez a cada X ms, ignorando chamadas subsequentes.

🧠 Ideal para: scroll, resize, cliques rápidos, etc.


🧱 Implementando debounce no React

// SearchInput.tsx
import { useEffect, useState } from 'react';

type Props = {
  onSearch: (query: string) => void;
};

export function SearchInput({ onSearch }: Props) {
  const [value, setValue] = useState('');

  useEffect(() => {
    const timeout = setTimeout(() => {
      onSearch(value);
    }, 500);

    return () => clearTimeout(timeout);
  }, [value, onSearch]);

  return (
    <input
      placeholder="Search..."
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

🧪 Testando debounce com jest.useFakeTimers()

// SearchInput.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { SearchInput } from './SearchInput';

jest.useFakeTimers();

test('calls onSearch after debounce delay', () => {
  const handleSearch = jest.fn();
  render(<SearchInput onSearch={handleSearch} />);

  const input = screen.getByPlaceholderText(/search/i);
  fireEvent.change(input, { target: { value: 'hello' } });

  // Ainda não chamou
  expect(handleSearch).not.toHaveBeenCalled();

  // Avança 500ms
  jest.advanceTimersByTime(500);

  expect(handleSearch).toHaveBeenCalledWith('hello');
});
Enter fullscreen mode Exit fullscreen mode

⚡ Implementando throttle com lodash

// ThrottledButton.tsx
import { throttle } from 'lodash';
import { useCallback } from 'react';

export function ThrottledButton({ onClick }: { onClick: () => void }) {
  const throttled = useCallback(throttle(onClick, 1000), [onClick]);

  return <button onClick={throttled}>Click me fast!</button>;
}
Enter fullscreen mode Exit fullscreen mode

🧪 Testando throttle com fake timers

// ThrottledButton.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ThrottledButton } from './ThrottledButton';

jest.useFakeTimers();

test('calls onClick only once in throttle window', () => {
  const handleClick = jest.fn();
  render(<ThrottledButton onClick={handleClick} />);

  const button = screen.getByText(/click me/i);
  fireEvent.click(button);
  fireEvent.click(button);
  fireEvent.click(button);

  expect(handleClick).toHaveBeenCalledTimes(1);

  jest.advanceTimersByTime(1000);
  fireEvent.click(button);

  expect(handleClick).toHaveBeenCalledTimes(2);
});
Enter fullscreen mode Exit fullscreen mode

🧠 Dicas rápidas

  • Use jest.useFakeTimers() antes de renderizar o componente.
  • Sempre finalize com jest.useRealTimers() se for fazer outros testes depois.
  • Debounce pode ser testado com setTimeout; throttle exige lib como lodash ou lodash.throttle.

✅ Conclusão

Testar debounce e throttle é mais fácil do que parece quando você usa as ferramentas certas: fake timers + eventos simulados. Evite confiar apenas no comportamento visual — escrever testes sólidos é o que garante que seu input não dispare chamadas desnecessárias no futuro.

Top comments (0)