Portafolio profesional
Testing en Angular: Jest y Cypress para Calidad Total
Testing Completo en Angular Una estrategia de testing sólida es esencial para aplicaciones mantenibles. Aprende a implementar tests unitarios con Jest y E2E con
Osman Jimenez
Testing en Angular: Jest y Cypress para Calidad Total
16 de diciembre de 2025
publicado
4 min
lectura
3 temas
Temas
- Angular
- Testing
- Calidad de Software
Testing Completo en Angular
Una estrategia de testing sólida es esencial para aplicaciones mantenibles. Aprende a implementar tests unitarios con Jest y E2E con Cypress.
Configurar Jest en Angular
// Instalar dependencias
npm install --save-dev jest @types/jest jest-preset-angular
// jest.config.js
module.exports = {
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['/setup-jest.ts'],
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.spec.ts',
'!src/main.ts'
]
};
// setup-jest.ts
import 'jest-preset-angular/setup-jest'; Tests Unitarios de Componentes
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture;
let userService: jest.Mocked;
beforeEach(() => {
const userServiceMock = {
getUsers: jest.fn()
};
TestBed.configureTestingModule({
imports: [UserListComponent],
providers: [
{ provide: UserService, useValue: userServiceMock }
]
});
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
userService = TestBed.inject(UserService) as jest.Mocked;
});
it('should load users on init', () => {
const mockUsers = [{ id: 1, name: 'John' }];
userService.getUsers.mockReturnValue(of(mockUsers));
fixture.detectChanges();
expect(component.users()).toEqual(mockUsers);
expect(userService.getUsers).toHaveBeenCalledTimes(1);
});
it('should display users in template', () => {
component.users.set([{ id: 1, name: 'John' }]);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.user-name').textContent).toContain('John');
});
}); Tests de Servicios
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should fetch users', (done) => {
const mockUsers = [{ id: 1, name: 'John' }];
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers);
done();
});
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
});
it('should handle errors', (done) => {
service.getUsers().subscribe({
error: (error) => {
expect(error.status).toBe(500);
done();
}
});
const req = httpMock.expectOne('/api/users');
req.flush('Error', { status: 500, statusText: 'Server Error' });
});
});Tests con Signals
describe('CounterComponent with Signals', () => {
it('should increment counter', () => {
const fixture = TestBed.createComponent(CounterComponent);
const component = fixture.componentInstance;
expect(component.count()).toBe(0);
component.increment();
expect(component.count()).toBe(1);
fixture.detectChanges();
const button = fixture.nativeElement.querySelector('button');
expect(button.textContent).toContain('1');
});
});Configurar Cypress
// Instalar
npm install --save-dev cypress
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
supportFile: 'cypress/support/e2e.ts',
specPattern: 'cypress/e2e/**/*.cy.ts'
},
component: {
devServer: {
framework: 'angular',
bundler: 'webpack'
},
specPattern: '**/*.cy.ts'
}
});Tests E2E con Cypress
// cypress/e2e/user-flow.cy.ts
describe('User Flow', () => {
beforeEach(() => {
cy.visit('/');
});
it('should login and view dashboard', () => {
// Login
cy.get('[data-cy=email]').type('user@example.com');
cy.get('[data-cy=password]').type('password123');
cy.get('[data-cy=login-btn]').click();
// Verificar redirección
cy.url().should('include', '/dashboard');
// Verificar contenido
cy.get('[data-cy=welcome-message]')
.should('contain', 'Welcome');
});
it('should create new user', () => {
cy.get('[data-cy=new-user-btn]').click();
cy.get('[data-cy=name-input]').type('John Doe');
cy.get('[data-cy=email-input]').type('john@example.com');
cy.get('[data-cy=submit-btn]').click();
// Verificar mensaje de éxito
cy.get('[data-cy=success-message]')
.should('be.visible')
.and('contain', 'User created');
// Verificar que aparece en la lista
cy.get('[data-cy=user-list]')
.should('contain', 'John Doe');
});
});Custom Commands en Cypress
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable;
createUser(name: string, email: string): Chainable;
}
}
}
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login');
cy.get('[data-cy=email]').type(email);
cy.get('[data-cy=password]').type(password);
cy.get('[data-cy=login-btn]').click();
cy.url().should('include', '/dashboard');
});
Cypress.Commands.add('createUser', (name, email) => {
cy.request('POST', '/api/users', { name, email });
});
// Uso
cy.login('user@example.com', 'password');
cy.createUser('John', 'john@example.com'); Interceptar Requests
describe('API Mocking', () => {
it('should mock API response', () => {
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
}).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-cy=user-list]')
.children()
.should('have.length', 2);
});
it('should handle API errors', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Server Error' }
});
cy.visit('/users');
cy.get('[data-cy=error-message]')
.should('be.visible');
});
});Component Testing con Cypress
// user-card.component.cy.ts
import { UserCardComponent } from './user-card.component';
describe('UserCardComponent', () => {
it('should display user info', () => {
cy.mount(UserCardComponent, {
componentProperties: {
user: { id: 1, name: 'John', email: 'john@example.com' }
}
});
cy.get('[data-cy=user-name]').should('contain', 'John');
cy.get('[data-cy=user-email]').should('contain', 'john@example.com');
});
it('should emit delete event', () => {
const onDeleteSpy = cy.spy().as('onDeleteSpy');
cy.mount(UserCardComponent, {
componentProperties: {
user: { id: 1, name: 'John' },
delete: onDeleteSpy
}
});
cy.get('[data-cy=delete-btn]').click();
cy.get('@onDeleteSpy').should('have.been.calledOnce');
});
});Mejores Prácticas
- Usa data-cy attributes: Para selectores estables
- Tests independientes: Cada test debe poder ejecutarse solo
- Arrange-Act-Assert: Estructura clara de tests
- Mock dependencies: Aisla lo que estás probando
- Coverage mínimo: Apunta a 80%+ en código crítico
- Tests rápidos: Unitarios < 1s, E2E < 30s
Conclusión
Una estrategia de testing completa con Jest para unitarios y Cypress para E2E te da confianza para refactorizar y agregar features. Invierte tiempo en tests y ahorrarás mucho más en debugging.