UNPKG

agent-rules-kit

Version:

Bootstrap of **Cursor** rules (`.mdc`) and mirror documentation (`.md`) for AI agent-guided projects.

258 lines (198 loc) 9.12 kB
--- description: Testing strategies, tools, and best practices for Angular applications. globs: <root>/src/**/*.spec.ts alwaysApply: true --- # Testing Angular Applications Testing is a fundamental part of developing high-quality Angular applications in {projectPath}. Angular is designed with testability in mind and provides a robust testing framework and utilities. ## The Testing Pyramid A balanced testing strategy typically follows the testing pyramid: 1. **Unit Tests (Most Numerous)**: - **Focus**: Test individual classes, methods, pipes, or functions in isolation. - **Speed**: Very fast. - **Tools**: Jasmine (framework), Karma (test runner for browser environment), Angular TestBed (for some isolated class tests without DOM). 2. **Component Tests (Integration/Shallow Integration Tests - Moderate Number)**: - **Focus**: Test Angular components: their templates, class logic, interaction with dependencies (often mocked), and user interactions. - **Speed**: Slower than unit tests, faster than E2E. - **Tools**: Angular TestBed, Jasmine. 3. **End-to-End (E2E) Tests (Fewest)**: - **Focus**: Test complete user flows through the application, simulating real user scenarios in a browser. - **Speed**: Slowest. - **Tools**: Traditionally Protractor (now deprecated by Angular team). Current recommendations include WebdriverIO, Cypress, or Playwright. Angular CLI can generate E2E setups for these. ## Key Testing Tools & Concepts - **Jasmine**: The default testing framework for Angular projects. It provides functions like `describe()`, `it()`, `expect()`, `beforeEach()`, `afterEach()`, spies (`spyOn()`), etc. - **Karma**: The default test runner. It executes tests in a browser environment, allowing you to test code that interacts with the DOM. - **Angular TestBed**: - Angular's primary utility for writing component tests. It creates an Angular module environment for the component under test. - Allows you to configure a testing module, declare components, provide mock services, and interact with the component. - Key methods: `TestBed.configureTestingModule()`, `TestBed.createComponent()`. - **`ComponentFixture<T>`**: - A handle on an instance of the tested component and its associated DOM element. - Provides access to the component instance (`fixture.componentInstance`), its `DebugElement` (`fixture.debugElement`), and methods to trigger change detection (`fixture.detectChanges()`). - **`DebugElement`**: An Angular abstraction that provides a way to inspect component and DOM element properties. `debugElement.nativeElement` gives access to the actual DOM element. - **Spies, Stubs, and Mocks**: - **Spies (`jasmine.createSpy()` or `spyOn(object, 'methodName')`)**: Track calls to functions, their arguments, and can optionally fake their implementation. - **Stubs**: Replace functions with pre-programmed behavior, often returning specific values. - **Mocks**: Objects that simulate the behavior of real dependencies. In Angular, you often provide mock implementations of services in `TestBed`. ## Writing Component Tests Component tests are crucial for ensuring your UI behaves as expected. ```typescript // my-greeter.component.ts import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-my-greeter', template: ` <h1>Hello, {{ name }}!</h1> <button (click)="greet.emit('Hi from ' + name)">Greet Parent</button> `, }) export class MyGreeterComponent { @Input() name: string = 'World'; @Output() greet = new EventEmitter<string>(); } // my-greeter.component.spec.ts import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; // For querying DOM elements import { MyGreeterComponent } from './my-greeter.component'; describe('MyGreeterComponent', () => { let component: MyGreeterComponent; let fixture: ComponentFixture<MyGreeterComponent>; let h1: HTMLElement; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [MyGreeterComponent], // Declare the component being tested }).compileComponents(); // Compile template and css fixture = TestBed.createComponent(MyGreeterComponent); component = fixture.componentInstance; // Get component instance h1 = fixture.debugElement.query(By.css('h1')).nativeElement; }); it('should create', () => { expect(component).toBeTruthy(); }); it('should display default name if no input provided', () => { fixture.detectChanges(); // Trigger change detection expect(h1.textContent).toContain('Hello, World!'); }); it('should display provided name via @Input', () => { component.name = 'Angular'; fixture.detectChanges(); expect(h1.textContent).toContain('Hello, Angular!'); }); it('should emit greet event when button is clicked', () => { spyOn(component.greet, 'emit'); // Spy on the EventEmitter component.name = 'Tester'; fixture.detectChanges(); const button = fixture.debugElement.query( By.css('button') ).nativeElement; button.click(); // Simulate button click expect(component.greet.emit).toHaveBeenCalledWith('Hi from Tester'); }); }); ``` ## Testing Services Services often contain business logic or data access that needs to be tested. ```typescript // data.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { of, throwError } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private http: HttpClient) {} getData() { return this.http.get('/api/data'); } postData(item: any) { if (!item) { return throwError(() => new Error('Item cannot be null')); } return this.http.post('/api/data', item); } } // data.service.spec.ts import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController, } from '@angular/common/http/testing'; import { DataService } from './data.service'; describe('DataService', () => { let service: DataService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], // Mock HttpClient providers: [DataService], }); service = TestBed.inject(DataService); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); // Ensure no outstanding HTTP requests }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should retrieve data successfully', () => { const mockData = [{ id: 1, name: 'Test' }]; service.getData().subscribe((data) => { expect(data).toEqual(mockData); }); const req = httpMock.expectOne('/api/data'); expect(req.request.method).toBe('GET'); req.flush(mockData); // Provide mock response }); it('should return an error if item is null for postData', (done) => { service.postData(null).subscribe({ error: (err) => { expect(err.message).toBe('Item cannot be null'); done(); }, }); }); }); ``` ## Testing Pipes ```typescript // initial.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'initial' }) export class InitialPipe implements PipeTransform { transform(value: string): string { return value ? value.charAt(0).toUpperCase() + '.' : ''; } } // initial.pipe.spec.ts import { InitialPipe } from './initial.pipe'; describe('InitialPipe', () => { let pipe: InitialPipe; beforeEach(() => { pipe = new InitialPipe(); }); it('create an instance', () => { expect(pipe).toBeTruthy(); }); it('should return the first letter capitalized with a dot', () => { expect(pipe.transform('john')).toBe('J.'); }); it('should return empty string for empty input', () => { expect(pipe.transform('')).toBe(''); }); }); ``` ## Code Coverage Angular CLI integrates with Karma to produce code coverage reports (often using Istanbul). - Run tests with coverage: `ng test --no-watch --code-coverage` - Reports are typically generated in a `coverage/` directory. - Aim for meaningful coverage, not just high percentages. Ensure critical logic paths are tested. ## Community Libraries for Testing - **Spectator (by Netanel Basal)**: Provides a higher-level API for testing Angular components, directives, and services, reducing boilerplate. - **ng-mocks (by Isaac Dat Dresner)**: A library for mocking dependencies in Angular tests with a focus on type safety and ease of use. ## End-to-End (E2E) Testing Considerations - **Selector Strategy**: Use robust selectors (e.g., `data-testid` attributes) that are less prone to break with UI changes. - **Page Object Model (POM)**: A design pattern to create an object repository for UI elements, making tests more readable and maintainable. - **Wait Strategies**: Use explicit waits for elements to appear or become interactive to avoid flaky tests. By implementing a comprehensive testing strategy using these tools and practices, you can ensure the reliability and quality of your Angular application in {projectPath}. ``` ```