En esta página
Testing básico con Vitest
Testing en Angular
El testing automatizado verifica que tu código funciona correctamente y previene regresiones. Angular 21 soporta Vitest como test runner moderno y rápido.
Tipos de tests
| Tipo | Que testa | Velocidad | Ejemplo |
|---|---|---|---|
| Unitario | Una función/clase aislada | Muy rápido | Servicios, pipes, utils |
| De componente | Un componente con template | Rápido | Renderizado, interacción |
| De integración | Varios componentes juntos | Medio | Flujos de usuario |
| E2E | La app completa en un navegador | Lento | Registro, login, compra |
Configurar Vitest
Angular 21 puede usar Vitest en lugar de Karma/Jasmine:
npm install -D vitest @analogjs/vitest-angular jsdomConfiguración en vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.spec.ts'],
setupFiles: ['src/test-setup.ts'],
},
});TestBed
TestBed es el entorno de testing de Angular. Configura un módulo de testing con los proveedores necesarios:
import { TestBed } from '@angular/core/testing';
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MiServicio],
});
});Testear servicios
Los servicios son los más fáciles de testear porque son clases puras:
describe('CalculadoraService', () => {
let service: CalculadoraService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CalculadoraService);
});
it('debe sumar dos números', () => {
expect(service.sumar(2, 3)).toBe(5);
});
it('debe manejar números negativos', () => {
expect(service.sumar(-1, 5)).toBe(4);
});
});Testear signals
Los signals se testean leyendo su valor directamente:
it('debe actualizar el signal', () => {
const service = TestBed.inject(ContadorService);
expect(service.valor()).toBe(0);
service.incrementar();
expect(service.valor()).toBe(1);
// Computed signals también
expect(service.esPar()).toBe(false);
});Testear pipes
Los pipes son funciones puras, ideales para testing:
import { TruncarPipe } from './truncar.pipe';
describe('TruncarPipe', () => {
const pipe = new TruncarPipe();
it('debe truncar texto largo', () => {
const texto = 'Este es un texto bastante largo para la prueba';
expect(pipe.transform(texto, 20)).toBe('Este es un texto bas...');
});
it('no debe truncar texto corto', () => {
expect(pipe.transform('Hola', 20)).toBe('Hola');
});
it('debe manejar string vacio', () => {
expect(pipe.transform('', 20)).toBe('');
});
});Testear componentes
Para componentes, crea un fixture que renderiza el componente:
import { ComponentFixture, TestBed } from '@angular/core/testing';
describe('Contador', () => {
let fixture: ComponentFixture<Contador>;
let component: Contador;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Contador],
}).compileComponents();
fixture = TestBed.createComponent(Contador);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('debe renderizar el valor inicial', () => {
const element = fixture.nativeElement;
expect(element.textContent).toContain('0');
});
});Mocks y stubs
Para aislar tests, usa mocks de dependencias:
const mockAuth = {
usuario: signal(null),
estaAutenticado: computed(() => false),
login: vi.fn(),
};
TestBed.configureTestingModule({
providers: [
MiServicio,
{ provide: AuthService, useValue: mockAuth },
],
});Buenas prácticas de testing
- Un assert por test — Cada test verifica una sola cosa
- Nombres descriptivos —
debe incrementar el valor cuando se llama a incrementar() - Arrange-Act-Assert — Estructura clara en cada test
- Testea comportamiento — No la implementación interna
- Empieza por servicios — Son los más fáciles y valiosos
Práctica
- Testea un servicio con signals: Crea un
NotasServicecon metodosagregar(),eliminar()y uncomputed()para el total. Escribe al menos 3 tests unitarios que verifiquen el comportamiento de los signals. - Testea un pipe: Implementa un pipe
capitalizary escribe tests para texto normal, texto vacio y texto con multiples palabras. - Usa mocks en un test: Crea un test para un servicio que dependa de
AuthService. Usa un mock con{ provide: AuthService, useValue: mockAuth }para aislar la dependencia.
En la siguiente leccion construiras un proyecto completo que integra todo lo aprendido.
// --- contador.service.ts ---
import { Injectable, signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ContadorService {
private readonly _valor = signal(0);
readonly valor = this._valor.asReadonly();
readonly esPar = computed(() => this._valor() % 2 === 0);
incrementar(): void {
this._valor.update(v => v + 1);
}
decrementar(): void {
this._valor.update(v => Math.max(0, v - 1));
}
resetear(): void {
this._valor.set(0);
}
}
// --- contador.service.spec.ts ---
import { describe, it, expect, beforeEach } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { ContadorService } from './contador.service';
describe('ContadorService', () => {
let service: ContadorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ContadorService);
});
it('debe iniciar en 0', () => {
expect(service.valor()).toBe(0);
});
it('debe incrementar el valor', () => {
service.incrementar();
service.incrementar();
expect(service.valor()).toBe(2);
});
it('no debe decrementar por debajo de 0', () => {
service.decrementar();
expect(service.valor()).toBe(0);
});
it('esPar debe derivar correctamente', () => {
expect(service.esPar()).toBe(true);
service.incrementar();
expect(service.esPar()).toBe(false);
service.incrementar();
expect(service.esPar()).toBe(true);
});
it('debe resetear a 0', () => {
service.incrementar();
service.incrementar();
service.resetear();
expect(service.valor()).toBe(0);
});
});
Inicia sesión para guardar tu progreso