Capítulo 16: Testes Automatizados de Componentes e Serviços com TestBed e Vitest
Capítulo 16: Testes Automatizados de Componentes e Serviços com TestBed e Vitest
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual da Suíte de Testes com TestBed e Vitest
flowchart TD
TestFile["Arquivo de Teste (*.spec.ts)"] --> VitestRunner["Vitest Test Runner (Ultra-Rápido via ESM)"]
subgraph TESTBED_ENV["Ambiente Simulado do Angular (TestBed)"]
TB["TestBed.configureTestingModule()"]
PROV["provideHttpClientTesting() / Mocks de Serviços"]
COMP["ComponentFixture<T>"]
end
VitestRunner --> TESTBED_ENV
COMP --> DOM["fixture.nativeElement / debugElement (Inspeção de DOM)"]
COMP --> INST["fixture.componentInstance (Estado TypeScript / Signals)"]
COMP --> SET["fixture.componentRef.setInput('prop', valor)"]
DOM & INST & SET --> Assertions["Asserções: expect().toBe() / expect().toContain()"]
style TestFile fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style VitestRunner fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style TESTBED_ENV fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style Assertions fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. A Nova Era de Testes no Angular: Vitest e TestBed Standalone
Historicamente, o ecossistema Angular dependia da dupla Karma e Jasmine para execução de testes unitários. No entanto, o Karma foi descontinuado pela comunidade devido à sua lentidão e necessidade de instanciar instâncias reais de navegadores para cada bateria de testes.
No Angular moderno, a comunidade e a engenharia adotaram o Vitest e o Jest sobre arquitetura ESM nativa:
- Execução Instantânea: Dezenas de vezes mais rápida que o Karma, aproveitando compilação sob demanda via Vite / esbuild.
- TestBed Otimizado: Com componentes Standalone, o
TestBednão precisa declarar módulos gigantescos. Você importa apenas o componente sob teste e os mocks necessários. - Suporte Nativo a Signals: APIs dedicadas como
fixture.componentRef.setInput()simplificam testes de componentes reativos.
⚙️ 2. Testando Componentes Baseados em Signals
Veja o teste completo de um componente Standalone que utiliza input(), computed() e emite eventos:
// contador-badge.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { describe, it, expect, beforeEach } from 'vitest';
import { ContadorBadgeComponent } from './contador-badge.component';
describe('ContadorBadgeComponent', () => {
let component: ContadorBadgeComponent;
let fixture: ComponentFixture<ContadorBadgeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContadorBadgeComponent] // Componente Standalone importado diretamente
}).compileComponents();
fixture = TestBed.createComponent(ContadorBadgeComponent);
component = fixture.componentInstance;
});
it('deve instanciar o componente com sucesso', () => {
expect(component).toBeTruthy();
});
it('deve atualizar a UI reativamente quando o Signal Input mudar', () => {
// 1. Atualiza o input usando a nova API de teste de signals
fixture.componentRef.setInput('totalNotificacoes', 5);
// 2. Dispara a detecção de mudanças
fixture.detectChanges();
// 3. Inspeciona o elemento renderizado no DOM
const badgeEl = fixture.debugElement.query(By.css('.badge-numero')).nativeElement;
expect(badgeEl.textContent.trim()).toBe('5');
});
it('deve emitir o evento ao clicar no botão de reset', () => {
let eventoDisparado = false;
// Subscreve no Output funcional
component.aoZerar.subscribe(() => {
eventoDisparado = true;
});
const botao = fixture.debugElement.query(By.css('button.btn-reset')).nativeElement;
botao.click();
expect(eventoDisparado).toBe(true);
});
});
📡 3. Testando Serviços com HttpTestingController
Para testar serviços que realizam requisições HTTP sem disparar chamadas de rede reais, utiliza-se o provideHttpClientTesting():
// cliente.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { ClienteService } from './cliente.service';
describe('ClienteService', () => {
let service: ClienteService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ClienteService,
provideHttpClient(),
provideHttpClientTesting()
]
});
service = TestBed.inject(ClienteService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
// Garante que não ficaram requisições pendentes sem resposta
httpMock.verify();
});
it('deve buscar clientes da API via GET', () => {
const mockClientes = [
{ id: 1, nome: 'Empresa Alfa' },
{ id: 2, nome: 'Empresa Beta' }
];
service.listarClientes().subscribe(clientes => {
expect(clientes.length).toBe(2);
expect(clientes[0].nome).toBe('Empresa Alfa');
});
// Intercepta a requisição esperada
const req = httpMock.expectOne('/api/clientes');
expect(req.request.method).toBe('GET');
// Responde com dados falsos simulados
req.flush(mockClientes);
});
});
⚡ 4. Testes Unitários Puros: Zero Sobrecarga de TestBed
Nem todo teste exige instanciar o TestBed. Para Pipes, Services utilitários e Funções de validação, teste diretamente instanciando a classe com new, garantindo execução em menos de 2 milissegundos:
// cpf-mask.pipe.spec.ts
import { describe, it, expect } from 'vitest';
import { CpfMaskPipe } from './cpf-mask.pipe';
describe('CpfMaskPipe (Teste Unitário Puro)', () => {
const pipe = new CpfMaskPipe();
it('deve formatar 11 dígitos no padrão CPF', () => {
const resultado = pipe.transform('12345678901');
expect(resultado).toBe('123.456.789-01');
});
it('deve retornar string vazia se o valor for nulo', () => {
expect(pipe.transform(null)).toBe('');
});
});
⚖️ 5. Comparativo: Karma/Jasmine vs Vitest
| Dimensão Técnica | Karma + Jasmine (Legado) | Vitest + Angular 18+ (Moderno) |
|---|---|---|
| Tempo de Inicialização | 5 a 15 segundos (Abre browser Chrome) | < 300 milissegundos (JSDOM / Happy-DOM) |
| Hot Module Reloading (Watch) | Lento, recompila bundles pesados | Instantâneo via ESM HMR |
| Suporte a ESM | Limitado / Requer Webpack | Nativo de primeira classe |
| Consumo de Memória | Alto (Processo Chrome + Node) | Mínimo (Thread isolada) |
| Status Oficial | Descontinuado pela equipe web | Recomendado pela indústria |
⚠️ 6. Armadilhas Comuns e Anti-Patterns
- Esquecer
fixture.detectChanges():- Erro: Modificar uma propriedade do componente e tentar verificar o DOM imediatamente. O DOM não atualizará sem a chamada explícita de
fixture.detectChanges().
- Erro: Modificar uma propriedade do componente e tentar verificar o DOM imediatamente. O DOM não atualizará sem a chamada explícita de
- Não chamar
httpMock.verify()noafterEach:- Deixar requisições HTTP sem
flush()pode causar efeitos colaterais silenciosos em outros arquivos de teste que executarem posteriormente.
- Deixar requisições HTTP sem
🛡️ 7. Boas Práticas e Qualidade Corporativa
- Foque em Comportamento, Não em Detalhes de Implementação: Teste o que o usuário vê e interage no DOM (botões, mensagens de erro, textos) em vez de inspecionar variáveis privadas da classe.
- Manter Cobertura em Regras Críticas: Priorize 100% de cobertura de testes em guardas funcionais, validadores de formulário e serviços contábeis ou de checkout.
📋 8. Cheat-Sheet: Resumo Executivo
// Configuração rápida
await TestBed.configureTestingModule({ imports: [MeuComp] }).compileComponents();
const fixture = TestBed.createComponent(MeuComp);
// Atualizar Signal Input
fixture.componentRef.setInput('titulo', 'Novo Título');
fixture.detectChanges();
// Mock HTTP
httpMock.expectOne('/api/dados').flush({ ok: true });