En esta página

Testing básico con Vitest

12 min lectura TextoCap. 5 — Producción

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 jsdom

Configuració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

  1. Un assert por test — Cada test verifica una sola cosa
  2. Nombres descriptivosdebe incrementar el valor cuando se llama a incrementar()
  3. Arrange-Act-Assert — Estructura clara en cada test
  4. Testea comportamiento — No la implementación interna
  5. Empieza por servicios — Son los más fáciles y valiosos

Práctica

  1. Testea un servicio con signals: Crea un NotasService con metodos agregar(), eliminar() y un computed() para el total. Escribe al menos 3 tests unitarios que verifiquen el comportamiento de los signals.
  2. Testea un pipe: Implementa un pipe capitalizar y escribe tests para texto normal, texto vacio y texto con multiples palabras.
  3. 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.

Testea lógica, no templates
Enfocate en testear servicios y lógica de negocio primero. Los tests de componentes con templates son más fragiles y costosos de mantener. Un buen servicio bien testeado es más valioso que muchos tests de UI.
Arrange-Act-Assert
Estructura tus tests en tres fases: Arrange (preparar datos), Act (ejecutar la accion) y Assert (verificar resultados). Esto hace que los tests sean claros y fáciles de leer.
// --- 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);
  });
});