agent-rules-kit
Version:
CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.
344 lines (276 loc) • 9.67 kB
Markdown
---
description: Testing best practices for Angular applications
globs: <root>/src/**/*.spec.ts
alwaysApply: false
---
This guide outlines the recommended testing practices for Angular applications in {projectPath}, covering unit testing, integration testing, and end-to-end testing patterns.
Angular comes with Jasmine and Karma pre-configured:
```typescript
// karma.conf.js
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
coverageReporter: {
dir: require('path').join(__dirname, './coverage/'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' },
{ type: 'lcov' }
],
check: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80
}
}
},
reporters: ['progress', 'kjhtml', 'coverage'],
browsers: ['Chrome'],
singleRun: false
});
};
```
```typescript
// user-profile.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { UserProfileComponent } from './user-profile.component';
describe('UserProfileComponent', () => {
let component: UserProfileComponent;
let fixture: ComponentFixture<UserProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserProfileComponent]
}).compileComponents();
fixture = TestBed.createComponent(UserProfileComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display user name when user is provided', () => {
// Arrange
const mockUser = {
id: '1',
name: 'John Doe',
email: 'john@example.com'
};
// Act
component.user = mockUser;
fixture.detectChanges();
// Assert
const nameElement = fixture.debugElement.query(By.css('[data-testid="user-name"]'));
expect(nameElement.nativeElement.textContent).toContain('John Doe');
});
it('should emit edit event when edit button is clicked', () => {
// Arrange
spyOn(component.edit, 'emit');
const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com' };
component.user = mockUser;
fixture.detectChanges();
// Act
const editButton = fixture.debugElement.query(By.css('[data-testid="edit-button"]'));
editButton.nativeElement.click();
// Assert
expect(component.edit.emit).toHaveBeenCalledWith(mockUser);
});
});
```
```typescript
// product-list.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { ProductListComponent } from './product-list.component';
import { ProductService } from '../services/product.service';
describe('ProductListComponent', () => {
let component: ProductListComponent;
let fixture: ComponentFixture<ProductListComponent>;
let productService: jasmine.SpyObj<ProductService>;
beforeEach(async () => {
const productServiceSpy = jasmine.createSpyObj('ProductService', ['getProducts']);
await TestBed.configureTestingModule({
declarations: [ProductListComponent],
providers: [
{ provide: ProductService, useValue: productServiceSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(ProductListComponent);
component = fixture.componentInstance;
productService = TestBed.inject(ProductService) as jasmine.SpyObj<ProductService>;
});
it('should load products on init', () => {
// Arrange
const mockProducts = [
{ id: '1', name: 'Product 1', price: 100 },
{ id: '2', name: 'Product 2', price: 200 }
];
productService.getProducts.and.returnValue(of(mockProducts));
// Act
component.ngOnInit();
// Assert
expect(productService.getProducts).toHaveBeenCalled();
expect(component.products).toEqual(mockProducts);
});
it('should handle error when loading products fails', () => {
// Arrange
const errorMessage = 'Failed to load products';
productService.getProducts.and.returnValue(throwError(errorMessage));
spyOn(console, 'error');
// Act
component.ngOnInit();
// Assert
expect(component.products).toEqual([]);
expect(component.error).toBe(errorMessage);
expect(console.error).toHaveBeenCalledWith('Error loading products:', errorMessage);
});
});
```
```typescript
// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should fetch users', () => {
// Arrange
const mockUsers = [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }
];
// Act
service.getUsers().subscribe(users => {
// Assert
expect(users).toEqual(mockUsers);
});
// Assert HTTP request
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
});
it('should create user', () => {
// Arrange
const newUser = { name: 'New User', email: 'new@example.com' };
const createdUser = { id: '3', ...newUser };
// Act
service.createUser(newUser).subscribe(user => {
// Assert
expect(user).toEqual(createdUser);
});
// Assert HTTP request
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(newUser);
req.flush(createdUser);
});
});
```
```typescript
// user-form.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { UserFormComponent } from './user-form.component';
describe('UserFormComponent', () => {
let component: UserFormComponent;
let fixture: ComponentFixture<UserFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserFormComponent],
imports: [ReactiveFormsModule]
}).compileComponents();
fixture = TestBed.createComponent(UserFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should validate required fields', () => {
// Act
component.userForm.get('name')?.setValue('');
component.userForm.get('email')?.setValue('');
// Assert
expect(component.userForm.get('name')?.hasError('required')).toBeTruthy();
expect(component.userForm.get('email')?.hasError('required')).toBeTruthy();
expect(component.userForm.invalid).toBeTruthy();
});
it('should validate email format', () => {
// Act
component.userForm.get('email')?.setValue('invalid-email');
// Assert
expect(component.userForm.get('email')?.hasError('email')).toBeTruthy();
});
it('should emit form data on valid submission', () => {
// Arrange
spyOn(component.formSubmit, 'emit');
component.userForm.patchValue({
name: 'John Doe',
email: 'john@example.com'
});
// Act
const form = fixture.debugElement.query(By.css('form'));
form.triggerEventHandler('ngSubmit', null);
// Assert
expect(component.formSubmit.emit).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com'
});
});
});
```
```typescript
// testing/test-utils.ts
import { ComponentFixture } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
export class TestingUtils {
static getByTestId<T>(fixture: ComponentFixture<T>, testId: string): DebugElement {
return fixture.debugElement.query(By.css(`[data-testid="${testId}"]`));
}
static clickElement<T>(fixture: ComponentFixture<T>, testId: string): void {
const element = this.getByTestId(fixture, testId);
element.nativeElement.click();
fixture.detectChanges();
}
static setInputValue<T>(fixture: ComponentFixture<T>, testId: string, value: string): void {
const input = this.getByTestId(fixture, testId);
input.nativeElement.value = value;
input.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
}
}
```
1. **Use TestBed.configureTestingModule()** for component and service testing
2. **Mock all dependencies** using jasmine.createSpyObj()
3. **Use data-testid attributes** for reliable element selection
4. **Test behavior, not implementation** details
5. **Keep tests isolated** - each test should be independent
6. **Use descriptive test names** that explain what is being tested
7. **Follow AAA pattern** (Arrange, Act, Assert)
8. **Test both happy path and error scenarios**
9. **Maintain good test coverage** (aim for 80%+ code coverage)