A Complete Guide to Angular Component Testing with Cypress
Raju Dandigam

Raju Dandigam @raju_dandigam

About: Engineering Manager with over 13 years of experience transitioning from a front-end engineer to a leadership role. Expertise in developing innovative front-end solutions, leading full-stack teams, and

Location:
Palo Alto, CA
Joined:
Jul 3, 2024

A Complete Guide to Angular Component Testing with Cypress

Publish Date: Apr 6
1 0

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

Comments 0 total

    Add comment