Implementando Testes com Jest em Projetos React + TypeScript
Tags: react, jest, typescript, testing
Published: 2024-01-03 17:43:20

## Introdução

Testar componentes e funções em projetos React não é apenas uma questão de encontrar bugs, mas também de garantir a estabilidade e previsibilidade do software ao longo do tempo. Jest, uma framework de testes robusta e de fácil utilização para JavaScript e TypeScript, oferece um caminho para isso. Este artigo explora a implementação de testes com Jest em projetos React, abordando desde testes de componentes isolados até testes de componentes que fazem requisições a APIs.


## Configuração Inicial

Para incorporar testes em um projeto React com TypeScript, começamos com a instalação e configuração do Jest e suas dependências. A integração do Jest com TypeScript, através do `ts-jest`, permite que aproveitemos a tipagem forte do TypeScript nos testes, aumentando a precisão e a confiabilidade dos mesmos.

Primeiro, precisamos configurar Jest no projeto. Supondo que você tenha um projeto React com TypeScript, você deve instalar o Jest e algumas dependências relacionadas:

```bash
npm install --save-dev jest ts-jest @types/jest @testing-library/react @testing-library/jest-dom
```

Em seguida, configure o Jest para trabalhar com TypeScript. No seu package.json, adicione:

```json
"jest": {
    "preset": "ts-jest",
    "testEnvironment": "jsdom",
    "setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"]
}
```

## Testando Componentes React de Forma Isolada

### O Componente `SimpleButton`

Considere um componente `SimpleButton`. Ele é um exemplo clássico de um componente de UI isolado, que podemos testar para garantir que renderiza corretamente e responde a eventos de usuário, como cliques.

```tsx
// src/components/SimpleButton.tsx

import React from 'react';

interface SimpleButtonProps {
    label: string;
    onClick: () => void;
}

export const SimpleButton: React.FC<SimpleButtonProps> = ({ label, onClick }) => (
    <button onClick={onClick}>{label}</button>
);
```

#### Testando a Renderização e Eventos

Nos testes, usamos `@testing-library/react` para renderizar o componente em um ambiente virtual e interagir com ele. Verificamos se o botão renderiza com o rótulo correto e se a função de clique é chamada quando necessário. Isso valida tanto a integridade visual do componente quanto a sua interatividade.

```tsx
// src/components/__tests__/SimpleButton.test.tsx

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { SimpleButton } from '../SimpleButton';

describe('SimpleButton', () => {
    it('renders the button with the given label', () => {
        render(<SimpleButton label="Click Me" onClick={() => {}} />);
        expect(screen.getByText('Click Me')).toBeInTheDocument();
    });

    it('calls the onClick handler when clicked', () => {
        const mockOnClick = jest.fn();
        render(<SimpleButton label="Click Me" onClick={mockOnClick} />);
        fireEvent.click(screen.getByText('Click Me'));
        expect(mockOnClick).toHaveBeenCalled();
    });
});
```

## Testando Lógica de Negócios com Funções Puras

### A Função `calculateSum`

Essa função, que soma dois números, é um exemplo de uma função pura em JavaScript/TypeScript. Testá-la significa garantir que, dadas entradas específicas, a saída é sempre a mesma. Essa previsibilidade é crucial em funções que constituem a lógica de negócios da aplicação.

Suponha que você tenha uma função simples:

```ts
// src/utils/calculateSum.ts

export const calculateSum = (a: number, b: number): number => {
    return a + b;
};
```

O teste seria:

```ts
// src/utils/__tests__/calculateSum.test.ts

import { calculateSum } from '../calculateSum';

describe('calculateSum', () => {
    it('correctly calculates the sum', () => {
        expect(calculateSum(2, 3)).toBe(5);
    });
});
```

## Testando Componentes com Requisições a APIs

### O Componente `UserList`

Quando se trata de componentes que fazem requisições a APIs, como `UserList`, o desafio é testar o comportamento do componente em resposta a dados externos. Aqui, o Jest oferece uma maneira de "mockar" essas requisições.

```tsx
// src/components/UserList.tsx

import React, { useState, useEffect } from 'react';
import { fetchUsers } from '../api/users';

export const UserList: React.FC = () => {
    const [users, setUsers] = useState([]);

    useEffect(() => {
        fetchUsers().then(data => setUsers(data));
    }, []);

    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
};
```

#### Mockando Requisições com Jest

Ao substituir a função `fetchUsers` real por uma versão mock, controlamos os dados retornados e podemos testar como o componente reage a esses dados. Isso é fundamental para testar o comportamento do componente em diferentes cenários, sem depender de uma API externa e sua disponibilidade ou estabilidade.


Para testá-lo, você pode mockar a função `fetchUsers`:

```tsx
// src/components/__tests__/UserList.test.tsx

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from '../UserList';
import * as api from '../../api/users';

jest.mock('../../api/users');

describe('UserList', () => {
    it('displays users fetched from the API', async () => {
        const mockUsers = [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Doe' }];
        (api.fetchUsers as jest.Mock).mockResolvedValue(mockUsers);

        render(<UserList />);

        await waitFor(() => {
            mockUsers.forEach(user => {
                expect(screen.getByText(user.name)).toBeInTheDocument();
            });
        });
    });
});
```

## Conclusão: O Valor dos Testes em Projetos React

A implementação de testes com Jest em projetos React e TypeScript é mais do que uma prática recomendada - é uma estratégia essencial para a construção de aplicações robustas e confiáveis. Testes de componentes isolados garantem que cada parte do sistema funcione corretamente em si mesma, enquanto testes de componentes com requisições a APIs garantem que eles se integrem de forma eficaz ao ecossistema maior da aplicação. Além disso, testes de funções puras asseguram a lógica de negócios consistente e previsível. A soma desses aspectos resulta em um software mais estável, sustentável e de fácil manutenção, consolidando as bases para um desenvolvimento ágil e seguro.