Pular para conteúdo

Testes do Frontend

Estrategia e estrutura de testes do frontend React do TepConfina.

Framework e Ferramentas

Ferramenta Funcao
Vitest Framework de testes unitarios
Testing Library Testes de componentes React
MSW Mock Service Worker para interceptar APIs
Playwright Testes end-to-end

Estrutura do Projeto

src/
  __tests__/
    utils/
      formatters.test.ts
      validators.test.ts
    services/
      authService.test.ts
      loteService.test.ts
    stores/
      authStore.test.ts
    components/
      Sidebar.test.tsx
      Header.test.tsx
    pages/
      LoginPage.test.tsx
      Dashboard.test.tsx
      LotesPage.test.tsx
e2e/
  global.setup.ts
  auth.spec.ts
  lotes.spec.ts
  dashboard.spec.ts

Testes Unitarios (100 testes)

Testes de Utilitarios

// formatters.test.ts
import { formatCurrency, formatWeight, formatDate } from '@/utils/formatters';

describe('formatCurrency', () => {
  it('deve formatar valor em reais', () => {
    expect(formatCurrency(1500.50)).toBe('R$ 1.500,50');
  });

  it('deve formatar zero', () => {
    expect(formatCurrency(0)).toBe('R$ 0,00');
  });

  it('deve formatar valores negativos', () => {
    expect(formatCurrency(-100)).toBe('-R$ 100,00');
  });
});

Testes de Validacao

// validators.test.ts
import { loteSchema } from '@/utils/validators';

describe('loteSchema', () => {
  it('deve validar lote com dados corretos', () => {
    const result = loteSchema.safeParse({
      nome: 'Lote 001',
      quantidadeAnimais: 30,
      pesoLiquidoEntrada: 15000,
      dataEntrada: '2026-01-15',
    });
    expect(result.success).toBe(true);
  });

  it('deve rejeitar quantidade de animais negativa', () => {
    const result = loteSchema.safeParse({
      nome: 'Lote 001',
      quantidadeAnimais: -5,
    });
    expect(result.success).toBe(false);
  });
});

Testes de Services com MSW

// loteService.test.ts
import { server } from '@/mocks/server';
import { http, HttpResponse } from 'msw';
import { loteService } from '@/services/loteService';

describe('loteService', () => {
  it('deve buscar lista de lotes', async () => {
    server.use(
      http.get('/api/lotes', () => {
        return HttpResponse.json([
          { id: '1', nome: 'Lote 001', status: 'Ativo' },
          { id: '2', nome: 'Lote 002', status: 'Fechado' },
        ]);
      })
    );

    const lotes = await loteService.listar();
    expect(lotes).toHaveLength(2);
    expect(lotes[0].nome).toBe('Lote 001');
  });
});

Testes de Store (Zustand)

// authStore.test.ts
import { useAuthStore } from '@/stores/authStore';

describe('authStore', () => {
  beforeEach(() => {
    useAuthStore.setState({ token: null, user: null });
  });

  it('deve armazenar token apos login', () => {
    useAuthStore.getState().setAuth({
      token: 'jwt-token',
      user: { id: '1', nome: 'Admin', role: 'Admin' },
    });

    expect(useAuthStore.getState().token).toBe('jwt-token');
    expect(useAuthStore.getState().isAuthenticated).toBe(true);
  });

  it('deve limpar estado apos logout', () => {
    useAuthStore.getState().setAuth({ token: 'jwt-token', user: {} });
    useAuthStore.getState().logout();

    expect(useAuthStore.getState().token).toBeNull();
    expect(useAuthStore.getState().isAuthenticated).toBe(false);
  });
});

Distribuicao dos Testes Unitarios

Categoria Qtd Descricao
Formatters 20 Formatacao de moeda, peso, data, porcentagem
Validation 15 Schemas zod para formularios
Services 25 Chamadas API com MSW
Stores 10 Estado global Zustand
Components 20 Renderizacao e interacao
Hooks 10 Hooks customizados

Testes E2E (22 testes)

Configuracao Global

// global.setup.ts
import { chromium } from '@playwright/test';

async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto('http://localhost:5173/login');
  await page.fill('[name="email"]', 'admin@tepconfina.com');
  await page.fill('[name="password"]', 'Admin@123');
  await page.click('button[type="submit"]');
  await page.waitForURL('/dashboard');

  await page.context().storageState({ path: 'e2e/.auth/state.json' });
  await browser.close();
}

export default globalSetup;

Exemplo de Teste E2E

// lotes.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Lotes', () => {
  test('deve listar lotes na pagina', async ({ page }) => {
    await page.goto('/lotes');
    await expect(page.getByRole('heading', { name: 'Lotes' })).toBeVisible();
    await expect(page.locator('table tbody tr')).toHaveCount.greaterThan(0);
  });

  test('deve criar novo lote', async ({ page }) => {
    await page.goto('/lotes/novo');
    await page.fill('[name="nome"]', 'Lote E2E Test');
    await page.fill('[name="quantidadeAnimais"]', '25');
    await page.click('button[type="submit"]');
    await expect(page.getByText('Lote criado com sucesso')).toBeVisible();
  });
});

Helpers de Teste

Render com Providers

import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';

export function renderWithProviders(ui: React.ReactElement) {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  return render(
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>{ui}</BrowserRouter>
    </QueryClientProvider>
  );
}

Executando os Testes

Comando Descricao
npx vitest run Executar todos os testes unitarios
npx vitest --watch Modo watch (desenvolvimento)
npx vitest --coverage Gerar relatorio de cobertura
npx playwright test Executar testes E2E
npx playwright test --ui Executar E2E com interface visual

Pre-requisito E2E

Os testes E2E requerem que o backend e o frontend estejam rodando localmente antes da execucao.