Angular's latest versions have introduced exciting features like signals, computed, and enhanced reactivity. Paired with Cypress Component Testing, developers now have a powerful toolkit to write fast, realistic, and robust tests. This post dives deep into Angular component testing using Cypress, including how to test components that use signals, observables, and mock services with confidence.
✨ Why Cypress for Angular Component Testing?
Cypress isn't just for end-to-end tests. With Cypress Component Testing, you can:
- Render Angular components in isolation
- Interact with them as a user would
- Spy on inputs/outputs
- Work seamlessly with Angular DI, modules, and modern features like signals
🔧 Setting Up Cypress Component Testing
Install Cypress and initialize component testing:
npm install cypress @cypress/angular --save-dev
npx cypress open --component
Follow the UI wizard to configure your Angular workspace. It will auto-generate a Cypress configuration compatible with Angular.
🛠️ Example Component with Signals & Outputs
Let's test a simple CounterComponent
that demonstrates:
- Input binding
- Output emission
- Signals and computed values
// counter.component.ts
import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
{{ title }}
Count: {{ count() }}
Increment
Reset
Double: {{ doubleCount() }}
`
})
export class CounterComponent {
@Input() title = 'Default Counter';
@Output() onReset = new EventEmitter();
private _count = signal(0);
count = this._count.asReadonly();
doubleCount = computed(() => this._count() * 2);
increment() {
this._count.set(this._count() + 1);
}
reset() {
this._count.set(0);
this.onReset.emit();
}
}
🔮 Basic Cypress Tests
✅ Rendering with Default and Custom Inputs
// counter.component.cy.ts
import { mount } from 'cypress/angular';
import { CounterComponent } from './counter.component';
describe('CounterComponent', () => {
it('renders with default title', () => {
mount(CounterComponent);
cy.contains('Default Counter');
cy.contains('Count: 0');
});
it('accepts custom title', () => {
mount(CounterComponent, {
componentProperties: { title: 'Custom Counter' }
});
cy.contains('Custom Counter');
});
});
⚖️ Testing Signals and Computed Values
it('increments count and updates double', () => {
mount(CounterComponent);
cy.get('button').contains('Increment').click().click();
cy.contains('Count: 2');
cy.contains('Double: 4');
});
📢 Spying on Output Events
it('emits reset event', () => {
const resetSpy = cy.spy().as('resetSpy');
mount(CounterComponent, {
componentProperties: {
onReset: { emit: resetSpy } as any
}
});
cy.get('button').contains('Reset').click();
cy.get('@resetSpy').should('have.been.calledOnce');
});
📃 Testing with Observables
// message.component.ts
@Component({
selector: 'app-message',
template: `{{ message }}`
})
export class MessageComponent {
private _message = signal('');
message = this._message.asReadonly();
@Input()
set messageInput$(value: Observable) {
value.subscribe(msg => this._message.set(msg));
}
}
it('renders observable message', () => {
const message$ = of('Hello from Observable!');
mount(MessageComponent, {
componentProperties: { messageInput$: message$ }
});
cy.contains('Hello from Observable!');
});
🔜 Advanced Mocking Techniques
⚙️ Injecting Angular Services
class MockLoggerService {
log = cy.stub().as('logStub');
}
mount(MyComponent, {
providers: [
{ provide: LoggerService, useClass: MockLoggerService }
]
});
🌐 Mocking HTTP Calls with HttpClientTestingModule
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
beforeEach(() => {
cy.intercept('GET', '/api/data', { fixture: 'data.json' });
});
mount(MyHttpComponent, {
imports: [HttpClientTestingModule]
});
🚪 Stubbing Child Components
@Component({
selector: 'app-child',
template: '' // stubbed template
})
class StubChildComponent {}
mount(ParentComponent, {
declarations: [StubChildComponent]
});
🚀 Conclusion
Cypress Component Testing is a game-changer for Angular developers. By combining the real browser testing experience with Angular’s new reactivity features like signals and computed, we can create robust, testable, and modern UIs.
Key Takeaways:
- Mount Angular components with Cypress easily
- Test signals, computed, observables
- Use spies and stubs for robust unit isolation
- Mock services and HTTP with Angular-friendly tooling