Mount and test Angular components with TestBed integration and Cypress testing commands. The @cypress/angular package provides seamless integration between Angular components and Cypress's powerful testing capabilities.
Mount Angular components for isolated testing with inputs, providers, and full Cypress command support.
// From cypress/angular
import { mount } from '@cypress/angular';
/**
* Mount an Angular component for testing
* @param component - Angular component class to mount
* @param options - Angular TestBed configuration options
* @returns Cypress chainable with fixture and component access
*/
function mount<T>(
component: Type<T>,
options?: AngularMountOptions<T>
): Cypress.Chainable<AngularMountReturn<T>>;
interface AngularMountOptions<T> extends TestModuleMetadata {
/** Component inputs (properties) */
componentProperties?: Partial<T>;
/** Flag to automatically create a cy.spy() for every component @Output() property */
autoSpyOutputs?: boolean;
/** Whether to log the mount command */
log?: boolean;
}
interface AngularMountReturn<T> {
/** Angular ComponentFixture for advanced testing */
fixture: ComponentFixture<T>;
/** Direct access to the component instance */
component: T;
}Usage Examples:
// cypress/component/button.component.cy.ts
import { mount } from '@cypress/angular';
import { ButtonComponent } from './button.component';
describe('ButtonComponent', () => {
it('renders with text', () => {
mount(ButtonComponent, {
componentProperties: {
text: 'Click me',
type: 'primary'
}
});
cy.contains('Click me').should('be.visible');
cy.get('button').should('have.class', 'btn-primary');
});
it('emits click events with manual spy', () => {
mount(ButtonComponent, {
componentProperties: {
text: 'Click me'
}
}).then(({ component }) => {
const clickSpy = cy.spy(component.clicked, 'emit');
cy.contains('Click me').click();
cy.wrap(clickSpy).should('have.been.called');
});
});
it('emits click events with autoSpyOutputs', () => {
mount(ButtonComponent, {
componentProperties: {
text: 'Click me'
},
autoSpyOutputs: true
});
cy.contains('Click me').click();
cy.get('@clickedSpy').should('have.been.called');
});
it('handles disabled state', () => {
mount(ButtonComponent, {
componentProperties: {
text: 'Disabled Button',
disabled: true
}
});
cy.get('button').should('be.disabled');
cy.get('button').should('have.class', 'btn-disabled');
});
});Test components that depend on Angular services through dependency injection.
import { mount } from '@cypress/angular';
import { UserService } from './user.service';
import { UserProfileComponent } from './user-profile.component';
describe('UserProfileComponent', () => {
it('displays user data from service', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com'
};
const mockUserService = {
getCurrentUser: () => of(mockUser),
updateUser: (user: any) => of(user)
};
mount(UserProfileComponent, {
providers: [
{ provide: UserService, useValue: mockUserService }
]
});
cy.contains('John Doe').should('be.visible');
cy.contains('john@example.com').should('be.visible');
});
it('handles service errors', () => {
const mockUserService = {
getCurrentUser: () => throwError('User not found'),
updateUser: (user: any) => of(user)
};
mount(UserProfileComponent, {
providers: [
{ provide: UserService, useValue: mockUserService }
]
});
cy.contains('Error loading user').should('be.visible');
});
});Test components that use Angular reactive forms or template-driven forms.
import { mount } from '@cypress/angular';
import { ReactiveFormsModule } from '@angular/forms';
import { LoginFormComponent } from './login-form.component';
describe('LoginFormComponent', () => {
it('validates required fields', () => {
mount(LoginFormComponent, {
imports: [ReactiveFormsModule]
});
cy.get('button[type=submit]').click();
cy.contains('Email is required').should('be.visible');
cy.contains('Password is required').should('be.visible');
cy.get('form').should('have.class', 'ng-invalid');
});
it('submits valid form', () => {
mount(LoginFormComponent, {
imports: [ReactiveFormsModule]
}).then(({ component }) => {
const submitSpy = cy.spy(component.formSubmit, 'emit');
cy.get('[data-cy=email-input]').type('user@example.com');
cy.get('[data-cy=password-input]').type('password123');
cy.get('button[type=submit]').click();
cy.wrap(submitSpy).should('have.been.calledWith', {
email: 'user@example.com',
password: 'password123'
});
});
});
it('displays validation errors', () => {
mount(LoginFormComponent, {
imports: [ReactiveFormsModule]
});
cy.get('[data-cy=email-input]').type('invalid-email');
cy.get('[data-cy=email-input]').blur();
cy.contains('Please enter a valid email').should('be.visible');
});
});Test components that use Angular Router for navigation.
import { mount } from '@cypress/angular';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { NavigationComponent } from './navigation.component';
describe('NavigationComponent', () => {
const routes = [
{ path: '', component: {} },
{ path: 'dashboard', component: {} },
{ path: 'profile', component: {} }
];
it('highlights active route', () => {
mount(NavigationComponent, {
providers: [
{ provide: Router, useValue: { url: '/dashboard' } },
{ provide: Location, useValue: { path: () => '/dashboard' } }
]
});
cy.get('[data-cy=nav-dashboard]').should('have.class', 'active');
cy.get('[data-cy=nav-profile]').should('not.have.class', 'active');
});
it('navigates to different routes', () => {
const mockRouter = {
navigate: cy.stub(),
url: '/'
};
mount(NavigationComponent, {
providers: [
{ provide: Router, useValue: mockRouter }
]
});
cy.get('[data-cy=nav-profile]').click();
cy.wrap(mockRouter.navigate).should('have.been.calledWith', ['/profile']);
});
});Test components that use Angular Material components.
import { mount } from '@cypress/angular';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ActionButtonComponent } from './action-button.component';
describe('ActionButtonComponent', () => {
it('renders Material Design button', () => {
mount(ActionButtonComponent, {
imports: [
BrowserAnimationsModule,
MatButtonModule,
MatIconModule
],
componentProperties: {
label: 'Save',
icon: 'save',
color: 'primary'
}
});
cy.get('button[mat-raised-button]').should('be.visible');
cy.get('mat-icon').should('contain', 'save');
cy.get('button').should('have.class', 'mat-primary');
});
it('handles different button variants', () => {
mount(ActionButtonComponent, {
imports: [BrowserAnimationsModule, MatButtonModule],
componentProperties: {
label: 'Delete',
variant: 'warn',
disabled: false
}
});
cy.get('button').should('have.class', 'mat-warn');
cy.get('button').should('not.be.disabled');
});
});Test components that make HTTP requests using Angular's HttpClient.
import { mount } from '@cypress/angular';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { DataService } from './data.service';
import { DataListComponent } from './data-list.component';
describe('DataListComponent', () => {
it('loads and displays data', () => {
const mockData = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
mount(DataListComponent, {
imports: [HttpClientTestingModule],
providers: [DataService]
}).then(({ fixture }) => {
const httpTestingController = fixture.debugElement.injector.get(HttpTestingController);
// Verify loading state
cy.contains('Loading...').should('be.visible');
// Mock HTTP response
const req = httpTestingController.expectOne('/api/data');
req.flush(mockData);
// Verify data is displayed
cy.contains('Item 1').should('be.visible');
cy.contains('Item 2').should('be.visible');
cy.get('[data-cy=data-item]').should('have.length', 2);
});
});
it('handles HTTP errors', () => {
mount(DataListComponent, {
imports: [HttpClientTestingModule],
providers: [DataService]
}).then(({ fixture }) => {
const httpTestingController = fixture.debugElement.injector.get(HttpTestingController);
// Mock HTTP error
const req = httpTestingController.expectOne('/api/data');
req.error(new ErrorEvent('Network error'));
// Verify error message
cy.contains('Failed to load data').should('be.visible');
});
});
});Test custom Angular directives in isolation.
import { mount } from '@cypress/angular';
import { Component } from '@angular/core';
import { HighlightDirective } from './highlight.directive';
@Component({
template: `
<div [appHighlight]="highlightColor" data-cy="highlighted-div">
Highlighted content
</div>
`
})
class TestComponent {
highlightColor = 'yellow';
}
describe('HighlightDirective', () => {
it('applies highlight color', () => {
mount(TestComponent, {
declarations: [HighlightDirective],
componentProperties: {
highlightColor: 'red'
}
});
cy.get('[data-cy=highlighted-div]')
.should('have.css', 'background-color', 'rgb(255, 0, 0)'); // red
});
it('responds to color changes', () => {
mount(TestComponent, {
declarations: [HighlightDirective]
}).then(({ component, fixture }) => {
// Initial color
cy.get('[data-cy=highlighted-div]')
.should('have.css', 'background-color', 'rgb(255, 255, 0)'); // yellow
// Change color
component.highlightColor = 'blue';
fixture.detectChanges();
cy.get('[data-cy=highlighted-div]')
.should('have.css', 'background-color', 'rgb(0, 0, 255)'); // blue
});
});
});Test custom Angular pipes in isolation.
import { mount } from '@cypress/angular';
import { Component } from '@angular/core';
import { TruncatePipe } from './truncate.pipe';
@Component({
template: `
<div data-cy="truncated-text">
{{ longText | truncate:maxLength }}
</div>
`
})
class TestComponent {
longText = 'This is a very long text that should be truncated';
maxLength = 20;
}
describe('TruncatePipe', () => {
it('truncates long text', () => {
mount(TestComponent, {
declarations: [TruncatePipe],
componentProperties: {
longText: 'This is a very long text that should be truncated',
maxLength: 20
}
});
cy.get('[data-cy=truncated-text]')
.should('contain', 'This is a very long...');
});
it('leaves short text unchanged', () => {
mount(TestComponent, {
declarations: [TruncatePipe],
componentProperties: {
longText: 'Short text',
maxLength: 20
}
});
cy.get('[data-cy=truncated-text]')
.should('contain', 'Short text');
});
});Configure Angular component testing in your Cypress configuration:
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'angular',
bundler: 'webpack',
},
specPattern: 'src/**/*.cy.ts',
supportFile: 'cypress/support/component.ts'
}
});// Testing with complex dependency injection
describe('Complex Component', () => {
it('handles multiple injected services', () => {
const mockAuthService = {
isAuthenticated: () => true,
getCurrentUser: () => of({ name: 'Test User' })
};
const mockNotificationService = {
showSuccess: cy.stub(),
showError: cy.stub()
};
mount(ComplexComponent, {
providers: [
{ provide: AuthService, useValue: mockAuthService },
{ provide: NotificationService, useValue: mockNotificationService }
]
}).then(({ component }) => {
cy.get('[data-cy=save-button]').click();
cy.wrap(mockNotificationService.showSuccess)
.should('have.been.calledWith', 'Saved successfully');
});
});
});The Angular component testing integration provides comprehensive support for testing Angular components in isolation while maintaining access to Angular's dependency injection system, change detection, and ecosystem tools like Angular Material and reactive forms.