Como testar hooks com vitest
Tags: webdev, testing, vitest, beginners
Published: 2024-03-15 17:47:41

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 ](https://uselist.netlify.app/)

## 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;

```
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]);
```

## 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([]);
  });
```

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([]);
  });
```

É 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yk5oflhrh53g7d3h48bx.png)

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