Claude
Skills
Sign in
Back

testing-strategies

Included with Lifetime
$97 forever

Comprehensive Angular testing strategies, patterns, and best practices for unit, integration, and E2E testing

General

What this skill does


# Angular Testing Strategies

Comprehensive guide to testing strategies, patterns, and best practices for Angular applications.

## Table of Contents

1. [Testing Pyramid](#testing-pyramid)
2. [Unit Testing Strategies](#unit-testing-strategies)
3. [Integration Testing](#integration-testing)
4. [E2E Testing](#e2e-testing)
5. [Test Organization](#test-organization)
6. [AAA Pattern](#aaa-pattern)
7. [Test Doubles](#test-doubles)
8. [Common Anti-Patterns](#common-anti-patterns)
9. [Performance Optimization](#performance-optimization)
10. [CI/CD Integration](#cicd-integration)

---

## Testing Pyramid

The ideal test distribution for Angular applications:

```
         ╱╲
        ╱ E2E ╲       (5% - Critical user journeys)
       ╱────────╲
      ╱Integration╲   (15% - Component + Service)
     ╱──────────────╲
    ╱   Unit Tests   ╲ (80% - Pure logic, services)
   ╱──────────────────╲
```

### Why This Distribution?

- **Unit Tests (80%)**: Fast, isolated, easy to maintain
- **Integration Tests (15%)**: Test component interactions
- **E2E Tests (5%)**: Slow but verify complete user flows

### Cost vs Coverage

| Test Type | Speed | Cost | Confidence | Maintenance |
|-----------|-------|------|------------|-------------|
| Unit | ⚡⚡⚡ | 💰 | ⭐⭐ | ✅ Easy |
| Integration | ⚡⚡ | 💰💰 | ⭐⭐⭐ | ⚠️ Medium |
| E2E | ⚡ | 💰💰💰 | ⭐⭐⭐⭐ | ❌ Hard |

---

## Unit Testing Strategies

### What to Unit Test

✅ **DO Test**:
- Pure functions and business logic
- Service methods
- Component methods (isolated)
- Pipes and custom validators
- Utility functions
- State management (reducers, selectors)

❌ **DON'T Test**:
- Angular framework internals
- Third-party libraries
- Simple getters/setters
- Generated code

### Component Testing Approaches

#### 1. Shallow Testing (Isolated)

Test component logic without rendering:

```typescript
describe('UserProfileComponent (Shallow)', () => {
  let component: UserProfileComponent;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    userService = jasmine.createSpyObj('UserService', ['getUser', 'updateUser']);
    component = new UserProfileComponent(userService);
  });

  it('should call updateUser with correct data', () => {
    const userData = { name: 'John', email: '[email protected]' };
    userService.updateUser.and.returnValue(of({ success: true }));

    component.saveProfile(userData);

    expect(userService.updateUser).toHaveBeenCalledWith(userData);
  });
});
```

**Pros**: Fast, focused on logic  
**Cons**: Doesn't test template integration  
**Use for**: Complex business logic, services

#### 2. Deep Testing (TestBed)

Test component with template and dependencies:

```typescript
describe('UserProfileComponent (Deep)', () => {
  let component: UserProfileComponent;
  let fixture: ComponentFixture<UserProfileComponent>;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(async () => {
    const spy = jasmine.createSpyObj('UserService', ['getUser', 'updateUser']);

    await TestBed.configureTestingModule({
      declarations: [ UserProfileComponent ],
      imports: [ ReactiveFormsModule, HttpClientTestingModule ],
      providers: [
        { provide: UserService, useValue: spy }
      ]
    }).compileComponents();

    userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
    fixture = TestBed.createComponent(UserProfileComponent);
    component = fixture.componentInstance;
  });

  it('should display user name in template', () => {
    const userData = { name: 'John Doe', email: '[email protected]' };
    userService.getUser.and.returnValue(of(userData));
    
    fixture.detectChanges(); // Trigger change detection

    const compiled = fixture.nativeElement;
    const nameElement = compiled.querySelector('.user-name');
    expect(nameElement.textContent).toContain('John Doe');
  });
});
```

**Pros**: Tests template + logic integration  
**Cons**: Slower, more complex setup  
**Use for**: UI interactions, template binding

### Service Testing Patterns

#### Simple Service (No HTTP)

```typescript
describe('CalculatorService', () => {
  let service: CalculatorService;

  beforeEach(() => {
    service = new CalculatorService();
  });

  it('should add two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });

  it('should throw error for division by zero', () => {
    expect(() => service.divide(10, 0)).toThrow();
  });
});
```

#### HTTP Service Testing

```typescript
describe('UserApiService', () => {
  let service: UserApiService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserApiService]
    });

    service = TestBed.inject(UserApiService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify(); // Ensure no outstanding requests
  });

  it('should fetch users', () => {
    const mockUsers = [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' }
    ];

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });

  it('should handle HTTP errors', () => {
    service.getUsers().subscribe({
      next: () => fail('should have failed'),
      error: (error) => {
        expect(error.status).toBe(500);
        expect(error.statusText).toBe('Server Error');
      }
    });

    const req = httpMock.expectOne('/api/users');
    req.flush('Error', { status: 500, statusText: 'Server Error' });
  });
});
```

#### Service with Dependencies

```typescript
describe('AuthService', () => {
  let service: AuthService;
  let http: jasmine.SpyObj<HttpClient>;
  let router: jasmine.SpyObj<Router>;

  beforeEach(() => {
    const httpSpy = jasmine.createSpyObj('HttpClient', ['post', 'get']);
    const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

    TestBed.configureTestingModule({
      providers: [
        AuthService,
        { provide: HttpClient, useValue: httpSpy },
        { provide: Router, useValue: routerSpy }
      ]
    });

    service = TestBed.inject(AuthService);
    http = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
    router = TestBed.inject(Router) as jasmine.SpyObj<Router>;
  });

  it('should login and navigate to dashboard', (done) => {
    const mockResponse = { token: 'fake-token' };
    http.post.and.returnValue(of(mockResponse));

    service.login('[email protected]', 'password').subscribe(() => {
      expect(http.post).toHaveBeenCalledWith(
        '/api/auth/login',
        { email: '[email protected]', password: 'password' }
      );
      expect(router.navigate).toHaveBeenCalledWith(['/dashboard']);
      done();
    });
  });
});
```

### Pipe Testing

```typescript
describe('FilterPipe', () => {
  let pipe: FilterPipe;

  beforeEach(() => {
    pipe = new FilterPipe();
  });

  it('should filter array by search term', () => {
    const items = [
      { name: 'Apple' },
      { name: 'Banana' },
      { name: 'Orange' }
    ];

    const result = pipe.transform(items, 'app', 'name');
    expect(result.length).toBe(1);
    expect(result[0].name).toBe('Apple');
  });

  it('should return empty array when no matches', () => {
    const items = [{ name: 'Apple' }];
    const result = pipe.transform(items, 'xyz', 'name');
    expect(result.length).toBe(0);
  });

  it('should return original array when search is empty', () => {
    const items = [{ name: 'Apple' }, { name: 'Banana' }];
    const result = pipe.transform(items, '', 'name');
    expect(result).toEqual(items);
  });
});
```

### Directive Testing

```typescript
describe('HighlightDirective', () => {
  let fixture: ComponentFixture<TestComponent>;
  let element: DebugElement;

  @Component({
    template: '<div appHighlight="yellow">Test</div>'
  })
  class TestComponent {}

  beforeEach(async () => {
    await TestBed.configure

Related in General