Arquitetura softwareFrontend webTypeScriptTestes
Unit & Integration Tests
Baixar PDFVitest, vi mocking, testes de hooks React, services e controllers — full-stack com exemplos práticos.
Unit & Integration Tests com Vitest
Configuração
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
test: {
environment: 'node', // ou 'jsdom' para React
globals: true,
setupFiles: ['./src/__tests__/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});Unit Tests — Funções puras
Validação
// validators/email.validator.ts
export function validateEmail(email: unknown): Result<string, string> {
if (typeof email !== 'string') return { ok: false, error: 'Email deve ser string' };
if (!email.includes('@')) return { ok: false, error: 'Email inválido' };
if (email.length < 5) return { ok: false, error: 'Email muito curto' };
return { ok: true, value: email };
}// validators/__tests__/email.validator.test.ts
import { describe, it, expect } from 'vitest';
import { validateEmail } from '../email.validator';
describe('validateEmail', () => {
it('aceita email válido', () => {
const result = validateEmail('[email protected]');
expect(result.ok).toBe(true);
if (result.ok) expect(result.value).toBe('[email protected]');
});
it('rejeita email sem @', () => {
const result = validateEmail('userexample.com');
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error).toBe('Email inválido');
});
it('rejeita valor não-string', () => {
expect(validateEmail(123).ok).toBe(false);
expect(validateEmail(null).ok).toBe(false);
expect(validateEmail(undefined).ok).toBe(false);
});
it('rejeita email muito curto', () => {
expect(validateEmail('a@b').ok).toBe(false);
});
});Transformação de dados
// utils/format-currency.ts
export function formatCurrencyBRL(amount: number): string {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(amount);
}// utils/__tests__/format-currency.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrencyBRL } from '../format-currency';
describe('formatCurrencyBRL', () => {
it('formata valores inteiros', () => {
expect(formatCurrencyBRL(100)).toBe('R$\u00a0100,00');
expect(formatCurrencyBRL(0)).toBe('R$\u00a00,00');
});
it('formata valores decimais', () => {
expect(formatCurrencyBRL(29.9)).toBe('R$\u00a029,90');
expect(formatCurrencyBRL(1234.56)).toBe('R$\u00a01.234,56');
});
it('formata valores negativos', () => {
expect(formatCurrencyBRL(-50)).toBe('-R$\u00a050,00');
});
});Unit Tests — Backend (Services com Mocks)
Mocking com vi
// services/order.service.ts
export class OrderService {
constructor(
private repo: OrderRepository,
private payment: PaymentService,
private notifier: NotificationService,
) {}
async create(dto: CreateOrderDto): Promise<Result<Order, string>> {
const paymentResult = await this.payment.charge(dto.paymentMethod, dto.total);
if (!paymentResult.success) {
return { ok: false, error: 'Pagamento recusado' };
}
const order = await this.repo.create({ ...dto, paymentId: paymentResult.id });
await this.notifier.sendOrderConfirmation(dto.userId, order.id);
return { ok: true, value: order };
}
}// services/__tests__/order.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { OrderService } from '../order.service';
// Cria mocks tipados
const mockRepo = {
create: vi.fn(),
findById: vi.fn(),
};
const mockPayment = {
charge: vi.fn(),
refund: vi.fn(),
};
const mockNotifier = {
sendOrderConfirmation: vi.fn(),
};
let service: OrderService;
beforeEach(() => {
vi.clearAllMocks(); // limpa estado entre testes
service = new OrderService(mockRepo, mockPayment, mockNotifier);
});
describe('OrderService.create', () => {
const dto = { userId: '1', items: [{ productId: 'p1', qty: 1 }], total: 100, paymentMethod: 'pix' };
it('cria pedido quando pagamento é aprovado', async () => {
mockPayment.charge.mockResolvedValue({ success: true, id: 'pay-123' });
mockRepo.create.mockResolvedValue({ id: 'order-1', ...dto });
const result = await service.create(dto);
expect(result.ok).toBe(true);
expect(mockRepo.create).toHaveBeenCalledOnce();
expect(mockNotifier.sendOrderConfirmation).toHaveBeenCalledWith('1', 'order-1');
});
it('falha quando pagamento é recusado', async () => {
mockPayment.charge.mockResolvedValue({ success: false });
const result = await service.create(dto);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error).toBe('Pagamento recusado');
expect(mockRepo.create).not.toHaveBeenCalled(); // não criou pedido
});
});Unit Tests — Frontend (Componentes React)
Componente com Testing Library
// components/__tests__/UserCard.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserCard } from '../UserCard';
const mockUser = {
id: '1',
name: 'Kaique Yamamoto',
email: '[email protected]',
createdAt: '2025-01-15T10:00:00Z',
};
describe('UserCard', () => {
it('renderiza nome e email do usuário', () => {
render(<UserCard user={mockUser} onSelect={() => {}} />);
expect(screen.getByText('Kaique Yamamoto')).toBeInTheDocument();
expect(screen.getByText('[email protected]')).toBeInTheDocument();
});
it('chama onSelect ao clicar', async () => {
const onSelect = vi.fn();
render(<UserCard user={mockUser} onSelect={onSelect} />);
await userEvent.click(screen.getByRole('button'));
expect(onSelect).toHaveBeenCalledWith('1');
});
it('mostra iniciais do nome', () => {
render(<UserCard user={mockUser} onSelect={() => {}} />);
expect(screen.getByText('KY')).toBeInTheDocument();
});
});Integration Tests — Backend (Service + Repository)
// __tests__/integration/user.service.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { MongoClient } from 'mongodb';
import { UserService } from '@/services/user.service';
let mongo: MongoMemoryServer;
let client: MongoClient;
let service: UserService;
beforeAll(async () => {
mongo = await MongoMemoryServer.create();
client = new MongoClient(mongo.getUri());
await client.connect();
const db = client.db('test');
service = new UserService(db.collection('users'));
});
afterAll(async () => {
await client.close();
await mongo.stop();
});
describe('UserService (integration)', () => {
it('cria e recupera usuário', async () => {
const user = await service.create({ name: 'Kaique', email: '[email protected]' });
expect(user.id).toBeDefined();
const found = await service.findById(user.id);
expect(found?.name).toBe('Kaique');
});
it('rejeita email duplicado', async () => {
await service.create({ name: 'Test', email: '[email protected]' });
const result = await service.create({ name: 'Test2', email: '[email protected]' });
expect(result.ok).toBe(false);
});
});