DEV Community

Genilson fernandes
Genilson fernandes

Posted on

Como testar hooks com vitest

O uso de hooks no React é uma prática valiosa para abstrair partes do nosso código, seja para realizar chamadas a APIs, lidar com cálculos complexos ou outras tarefas. entre tanto os hooks sejam extremamente úteis, é importante garantir que funcionem corretamente, especialmente em um ambiente controlado onde podemos definir a entrada e controlar a saída.

Abaixo criei um hook super simples que gerencia um estado de uma lista, algo comum no dia a dia como deve, nesse exemplo, vou mostrar a implementação e como testar, e as partes que estão sendo testadas.

aqui o exemplo de uso

Esse aqui é o hook

import { useState } from 'react';

const useList = <T>(initialList: T[]) => {
  const [list, setList] = useState<T[]>(initialList);

  const onToggle = (id: T) => {
    if (list.includes(id)) {
      setList(list.filter((item) => item !== id));
    } else {
      setList([...list, id]);
    }
  };

  const onSelectAll = (ids: T[]) => {
    setList(ids);
  };

  const isSelected = (id: T) => {
    return list.includes(id);
  };

  const onClear = () => {
    setList([]);
  };

  return { list, onToggle, onSelectAll, onClear, isSelected };
};

export default useList;

Enter fullscreen mode Exit fullscreen mode

Como pode ver, ele retorna uma lista de ids em um array, e tem os métodos para controlar a lista: onSelectAll, isSelected, onClear e list.

Assim fica a chamada do hook

const { 
list,
onSelectAll,
onClear,
onToggle } = useList<number>([1, 2]);
Enter fullscreen mode Exit fullscreen mode

Mas vamos os testes….

Primeiro de tudo, não vou ensinar como configura o ambiente de teste, não é difícil de fazer, com o vitest é quase automático.

Detalhes, antes de começar, o testing-library oferece dois métodos para gente testar hooks: act, renderHook, sendo o ACT, para lidar com atualizações de estado que causa efeito colaterais e o renderHook, como nome já diz, ele renderizar nosso hook em diferentes situações

it('should initialize with provided initial list', () => {
    const initialList = [1, 2, 3];
    const { result } = renderHook(() => useList<number>(initialList));
    expect(result.current.list).toEqual(initialList);
  });

  it('should toggle items in the list', () => {
    const { result } = renderHook(() => useList<number>([1, 2, 3]));
    act(() => {
      result.current.onToggle(2);
    });
    expect(result.current.list).toEqual([1, 3]);
    act(() => {
      result.current.onToggle(4);
    });
    expect(result.current.list).toEqual([1, 3, 4]);
  });

  it('should toggle all items in the list', () => {
    const { result } = renderHook(() => useList<number>([1, 2, 3]));
    act(() => {
      result.current.onSelectAll([1, 2, 3, 4]);
    });
    expect(result.current.list).toEqual([1, 2, 3, 4]);
    act(() => {
      result.current.onClear();
    });
    expect(result.current.list).toEqual([]);
  });
Enter fullscreen mode Exit fullscreen mode

Como gosto de fazer, é que o teste seja autoexplicativo, trazendo consistência, seja para mim ou para quem vai ver o código e fazer modificações.

  • No primeiro caso, estamos testando se o hook foi iniciado corretamente, sem novidades até aqui.
  • No segundo caso, vamos iniciar o hook com uma lista de 3 itens e, em seguida, desmarcar o item 2. Como pode ser visto, essas ações que vão afetar o estado estão envoltas em um act, e em seguida o expect, que nesse caso, esperamos uma lista sem o id 2.
  • No terceiro caso, vamos marcar todos os itens e em seguida testar o terceiro método junto, que é o onClear. Como pode ser visto, esperamos um array vazio.
 it('should check if an item is selected', () => {
    const { result } = renderHook(() => useList<number>([1, 2, 3]));
    expect(result.current.isSelected(2)).toBe(true);
    expect(result.current.isSelected(4)).toBe(false);
  });

  it('should clear the list', () => {
    const { result } = renderHook(() => useList([1, 2, 3]));
    act(() => {
      result.current.onClear();
    });
    expect(result.current.list).toEqual([]);
  });
Enter fullscreen mode Exit fullscreen mode

É comum acabarmos testando algo duas vezes, e neste caso, o onClear já tinha sido testado no caso 3. No entanto, para manter uma consistência e garantir que cada caso tenha seu próprio teste, vamos repeti-lo no caso 5.

  • Caso 4: Este caso testa a função que verifica se um item existe na lista. Veja que estamos fazendo algo novo aqui, que é primeiro testar o valor esperado e, em seguida, testar também um valor que não estará na lista. Isso garante a cobertura de ambos os casos, sendo importante nesta função.
  • Caso 5: Como mencionado, este caso já foi testado acima, então vou economizar palavras.

coverage

Assim ficou a coverage desse hook, apesar de simples, é importante testar essas pequenas abstrações do código, é isso hoje.

Top comments (1)

Collapse
 
matheusmangueira profile image
Matheus Mangueira

Muito bom, estava precisando de um artigo desse. Me ajudou bastante!!