[pt-BR] Como Testar Hooks de uma Aplicação em React - #3
Tags: javascript, beginners, tutorial, react
Published: 2022-02-21 16:48:31

Testes de hooks é algo que assusta muita gente, mas vamos tornar tudo mais simples a partir de agora!

Vamos continuar implementando testes na nossa aplicação de carrinho de ecommerce. Caso você não tenha visto o início dessa parte mais prática, confira na [parte 2](https://dev.to/dxwebster/pt-br-parte-2-hands-on-como-implementar-testes-unitarios-no-react-25jg) deste material.

### O que será abordado:
- Testando Hooks
- Simulação e mocks
- Testando ações
- Testando um Componente que utiliza um Hook

# Testando Hooks

O hook deste carrinho possui um contexto que nos fornece as ações de listar produtos, adicionar e remover os itens do carrinho e calcular o valor total dos itens.

A ideia é criar pelo menos 1 teste para cada ação, e utilizar os estados fornecidos pelo contexto para adicionar os mocks. Vamos ver o código do contexto (Vou deixar comentado em código o que cada parte faz):

```js
[...]

function CartProvider({ children }) {

// Estado que armazena os produtos
const [products, setProducts] = useState<CartItem[]>(() => {
    // pega os items em local storage
    const itemsStorage = localStorage.getItem('@UnitTest:cart');
    // se tiver items, retorna o valor como estado inicial
    if (itemsStorage) return JSON.parse(itemsStorage);
    // Se não tiver, retorna e inicia vazio.
    return [];
  });

  // Atualiza Local Storage com um novo array de itens
  useEffect(() => {
     localStorage.setItem('@UnitTest:cart', 
     JSON.stringify(products: CartItem[]));
  }, [products]);

// Ação que adiciona um item no carrinho
 const addToCart = useCallback(
     // Verifica se esse produto já existe no carrinho
     (item: Product) => {
     const productReceived = products.find(
        product => product.id === item.id
     );

     // Se sim, adiciona 1 na contagem do item existente
     if (!!productReceived) {
       productReceived.count += 1;
       setProducts([...products]);
     }
 
     // Se não, inclui o item no array com 1 no contador.
     else {
       setProducts([...products, { ...item, count: 1 }]);
     }
    },
 [products],
 );

[...]
```


## Simulações e mocks

Certo! Agora, vamos implementar o primeiro teste deste hook que será a funcionalidade de pegar os itens que estão salvos em Local Storage.

Lembrando que esses items são armazenados no estado `products`, então o que esperamos neste teste, é que o e estado `products` tenha um array com algum item adicionado.

Esse teste vai se chamar getItems from LocalStorage. O código abaixo descreve esse teste, e você pode perceber que ele segue o mesmo padrão de escrita de testes que já aprendemos.

```js
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { waitFor, act } from '@testing-library/react';

import { CartProvider, useCart } from '.';

describe('Cart hook tests', () => {
  test('get items from localStorage', async () => {
    
    // 1. renderizar o hook
    const { result } = renderHook(() => useCart(), {
      wrapper: CartProvider
    });

    // 2. fazer uma query ou mock
    const itemAddedToCart = {
      id: 1,
      createdAt: 'some_value',
      name: 'Product Test',
      price: 90,
      image: 'image_path',
      stock: 9,
    };

    // 3. executar alguma ação
    jest.spyOn(Storage.prototype,'getItem')
    .mockImplementation(key => {
      switch (key) {
        case '@ReactUnitTest:cart':
          return JSON.stringify([itemAddedToCart]);
        default:
          return null;
      }
    });

    // 4. descrever o resultado esperado
    await waitFor(() => {
      expect(result.current.products[0].name)
      .toEqual('Product Test');
    });
  });
});
```
`renderHook`: como hook não é um componente, utilizamos esse método para renderizar o Hook e conseguir pegar o resultado dele. Neste caso, importamos o useCart e o envolvemos pelo seu provider o CartProvider.

`result`: pega o resultado de alguma ação ou estado que veio do contexto. No caso, pegar o estado `products`.

`jest.spyOn`: faz a simulação de alguma funcionalidade, neste caso simulamos o Local Storage.

`mockImplementation`: implementa um mock na simulação, neste caso, implementamos o mock de item na simulação de LocalStorage

`waitFor`: espera por um resultado.

`expect`: descreve o resultado esperado, aqui nós esperamos que o resultado, ou seja, que o array `products` tenha um item com o nome igual ao que passamos no mock.

## Testando ações

Nosso próximo teste do hook será sobre adicionar items no carrinho. Queremos garantir que a function `addToCart` está funcionando corretamente.

O início será bem parecido com o teste anterior, da qual vamos renderizar o hook e criar um mock do item. O resultado esperado também será o mesmo, o que vai mudar aqui vai ser justamente o fato de que queremos testar uma açao do hook. Vamos ver como fica:

 ```js
test('get items from localStorage', async () => {
    
    // 1. renderizar o hook
    const { result } = renderHook(() => useCart(), {
      wrapper: CartProvider
    });

    // 2. fazer uma query ou mock
    const itemAddedToCart = {
      id: 1,
      createdAt: 'some_value',
      name: 'Product Test',
      price: 90,
      image: 'image_path',
      stock: 9,
    };

    // 3. executar alguma ação
    act(() => {
      result.current.addToCart(itemAddedToCart);
    });

    // 4. descrever o resultado esperado
    await waitFor(() => {
      expect(result.current.products[0].name)
      .toEqual('Product Test');
    });
  });
```

`act`: faz com que a renderização seja o mais próximo possível do que acontece no Browser. Neste caso, chamamos no método act a ação que queremos testar e passamos pra ela o mock como parâmetro.

`expect`: se tudo ocorrer bem, o resultado também será um array `products` com 1 item adicionado.

O hook ainda tem duas ações para ser testadas: removeToCart e getTotalPrice. Entretanto, fica como desafio, pois da pra fazer baseado naquilo que já vimos aqui. 

## Testando um Componente que utiliza um Hook

Ok, testamos duas funcionalidades do hook useCart e agora vamos testar um componente que utiliza esse hook, o próprio carrinho de compras. Como sempre, vamos olhar o código do componente:

```js
import React from 'react';

import { ReactComponent as CartIcon } from '../../../assets/cart.svg';
import { useCart } from '../../../hooks/Cart';
import { Container } from './styles';

export default function Cart() {
  const { products } = useCart();

  return (
    <Container to="/cart" data-testid="cart_link_component">
      <CartIcon />
      {products.length > 0 && (
        <div>
          <span>{products.length}</span>
        </div>
      )}
    </Container>
  );
};
```

Você se lembra qual o primeiro teste que costumamos escrever quando testamos um componente? Isso mesmo, testamos se ele está sendo exibido corretamente na tela. 

Entretanto, como esse componente utiliza o hook useCart, não podemos simplesmente testar como já fizemos nos anteriores. Aqui nós precisaremos mockar o hook, já que o componente depende dele.

Vamos utilizar o mesmo método que usamos para simular o LocalStorage, o `jest.spyOn`. E para mockar também o retorno do hook, vamos passar um objeto pelo método `mockReturnValue` com o estado `products` e as ações que o hook disponibiliza.

```js
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import * as hookCart from '../../../hooks/Cart';

describe('Cart component tests', () => {
  test('renders without crashing', () => {
    jest.spyOn(hookCart, 'useCart').mockReturnValue({
      products: [],
      addToCart: jest.fn(),
      removeToCart: jest.fn(),
      getTotalPrice: 0,
    });

    const { getByTestId } = render(<Cart />, {
      wrapper: BrowserRouter
    });
 
   expect(getByTestId('cart_link_component')).toBeInTheDocument();
  });
}
```
`import *`: vamos importar tudo que vem do hook para simular

`jest.spyOn`: passando o que foi importado, vamos simular o hook useCart e mockar no mockReturnValue seus valores.

`expect`: utilizamos o mesmo método anterior de getByTestId e esperamos que o componente esteja na tela. 


### 👉 Veja como testar consumo de API na [parte 4](https://dev.to/dxwebster/pt-br-parte-4-hands-on-como-testar-consumo-de-api-de-uma-aplicacao-em-react-2p51)

---

### Referências:
Esse conteúdo foi baseado no vídeo [“Treinamento: Como implementar testes unitários em React utilizando Jest”](https://youtu.be/wTrjx54wDgM](https://youtu.be/wTrjx54wDgM)) do canal da Zappts, feito pelo [Cláudio Castro](https://www.linkedin.com/in/ccastrojr/).

Confira o repositório com o projeto: https://github.com/ccastrojr/react_unit_test