DEV Community

Raju Dandigam
Raju Dandigam

Posted on

A Complete Guide to Angular Component Testing with Cypress

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
Enter fullscreen mode Exit fullscreen mode

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: `
    <div>
      <h2>{{ title }}</h2>
      <p>Count: {{ count() }}</p>
      <button (click)="increment()">Increment</button>
      <button (click)="reset()">Reset</button>
      <p>Double: {{ doubleCount() }}</p>
    </div>
  `
})
export class CounterComponent {
  @Input() title = 'Default Counter';
  @Output() onReset = new EventEmitter<void>();

  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();
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฎ 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');
  });
});
Enter fullscreen mode Exit fullscreen mode

โš–๏ธ 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');
});
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ข 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');
});
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ƒ Testing with Observables

// message.component.ts
@Component({
  selector: 'app-message',
  template: `<p *ngIf="message">{{ message }}</p>`
})
export class MessageComponent {
  private _message = signal('');
  message = this._message.asReadonly();

  @Input()
  set messageInput$(value: Observable<string>) {
    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!');
});
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”œ Advanced Mocking Techniques

โš™๏ธ Injecting Angular Services

class MockLoggerService {
  log = cy.stub().as('logStub');
}

mount(MyComponent, {
  providers: [
    { provide: LoggerService, useClass: MockLoggerService }
  ]
});
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ 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]
});
Enter fullscreen mode Exit fullscreen mode

๐Ÿšช Stubbing Child Components

@Component({
  selector: 'app-child',
  template: '' // stubbed template
})
class StubChildComponent {}

mount(ParentComponent, {
  declarations: [StubChildComponent]
});
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ 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

Top comments (0)