> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Comprehensive guide to testing Angular applications with TestBed, fixtures, and modern testing patterns

## Overview

Angular provides a robust testing infrastructure built on Jasmine and Karma (or Jest). The testing utilities make it easy to write unit tests, integration tests, and end-to-end tests for your applications.

## Testing Setup

### Basic Test Structure

Angular tests follow a standard pattern using Jasmine's `describe` and `it` blocks.

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';

describe('MyComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MyComponent]
    }).compileComponents();
  });

  it('should create', () => {
    const fixture = TestBed.createComponent(MyComponent);
    const component = fixture.componentInstance;
    expect(component).toBeTruthy();
  });
});
```

## TestBed

TestBed is Angular's primary testing utility that creates a testing module for your components.

### Configuring TestBed

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { UserService } from './user.service';
import { UserComponent } from './user.component';

describe('UserComponent', () => {
  let service: UserService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        UserComponent,
        HttpClientTestingModule,
        BrowserAnimationsModule
      ],
      providers: [
        UserService
      ]
    }).compileComponents();

    service = TestBed.inject(UserService);
  });

  it('should inject service', () => {
    expect(service).toBeTruthy();
  });
});
```

### Standalone Components

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { StandaloneComponent } from './standalone.component';

describe('StandaloneComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [StandaloneComponent],
      providers: [
        provideHttpClient(),
        provideAnimations()
      ]
    }).compileComponents();
  });

  it('should work', () => {
    const fixture = TestBed.createComponent(StandaloneComponent);
    expect(fixture.componentInstance).toBeTruthy();
  });
});
```

## ComponentFixture

ComponentFixture provides access to the component instance and its DOM.

### Basic Usage

```typescript theme={null}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;
  let compiled: HTMLElement;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    compiled = fixture.nativeElement;
  });

  it('should display counter value', async () => {
    component.count.set(5);
    await fixture.whenStable();
    
    expect(compiled.textContent).toContain('Count: 5');
  });

  it('should increment counter', async () => {
    component.increment();
    await fixture.whenStable();
    
    expect(component.count()).toBe(1);
  });
});
```

### Act, Wait, Assert Pattern

For zoneless and modern Angular applications, use the Act-Wait-Assert pattern.

```typescript theme={null}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { AsyncComponent } from './async.component';

describe('AsyncComponent (Zoneless)', () => {
  let fixture: ComponentFixture<AsyncComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AsyncComponent],
      providers: [
        provideZonelessChangeDetection()
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(AsyncComponent);
  });

  it('should load data', async () => {
    // Act: Trigger the action
    fixture.componentInstance.loadData();
    
    // Wait: Wait for async operations to complete
    await fixture.whenStable();
    
    // Assert: Verify the result
    expect(fixture.componentInstance.data()).toBeTruthy();
    expect(fixture.nativeElement.textContent).toContain('Loaded');
  });
});
```

<Note>
  In zoneless applications, always use `await fixture.whenStable()` instead of `fixture.detectChanges()`.
</Note>

## Testing Services

### Simple Service

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { CalculatorService } from './calculator.service';

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

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(CalculatorService);
  });

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

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

  it('should multiply numbers', () => {
    expect(service.multiply(4, 3)).toBe(12);
  });
});
```

### Service with Dependencies

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';

interface User {
  id: number;
  name: string;
}

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', () => {
    const mockUsers: User[] = [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' }
    ];

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

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

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

    const req = httpMock.expectOne('/api/users');
    req.flush('Not found', { status: 404, statusText: 'Not Found' });
  });
});
```

## Testing Components with Signals

```typescript theme={null}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { signal } from '@angular/core';
import { TodoComponent } from './todo.component';

describe('TodoComponent with Signals', () => {
  let component: TodoComponent;
  let fixture: ComponentFixture<TodoComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TodoComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TodoComponent);
    component = fixture.componentInstance;
  });

  it('should add todo', async () => {
    const initialCount = component.todos().length;
    
    component.addTodo('New task');
    await fixture.whenStable();
    
    expect(component.todos().length).toBe(initialCount + 1);
    expect(component.todos()[initialCount].title).toBe('New task');
  });

  it('should toggle todo', async () => {
    component.addTodo('Task 1');
    await fixture.whenStable();
    
    const todoId = component.todos()[0].id;
    component.toggleTodo(todoId);
    await fixture.whenStable();
    
    expect(component.todos()[0].completed).toBe(true);
  });

  it('should compute stats correctly', async () => {
    component.addTodo('Task 1');
    component.addTodo('Task 2');
    await fixture.whenStable();
    
    expect(component.stats().total).toBe(2);
    expect(component.stats().active).toBe(2);
    expect(component.stats().completed).toBe(0);
    
    component.toggleTodo(component.todos()[0].id);
    await fixture.whenStable();
    
    expect(component.stats().active).toBe(1);
    expect(component.stats().completed).toBe(1);
  });
});
```

## Mocking Dependencies

### Mock Services

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { DataService } from './data.service';
import { DataComponent } from './data.component';

describe('DataComponent with Mock', () => {
  let mockDataService: jasmine.SpyObj<DataService>;

  beforeEach(async () => {
    mockDataService = jasmine.createSpyObj('DataService', ['getData']);
    mockDataService.getData.and.returnValue(of(['item1', 'item2']));

    await TestBed.configureTestingModule({
      imports: [DataComponent],
      providers: [
        { provide: DataService, useValue: mockDataService }
      ]
    }).compileComponents();
  });

  it('should load data from service', async () => {
    const fixture = TestBed.createComponent(DataComponent);
    await fixture.whenStable();
    
    expect(mockDataService.getData).toHaveBeenCalled();
    expect(fixture.componentInstance.items.length).toBe(2);
  });
});
```

### Mock Providers

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { ProtectedComponent } from './protected.component';

class MockAuthService {
  isAuthenticated = true;
  currentUser = { id: 1, name: 'Test User' };
  
  login(credentials: any) {
    return Promise.resolve(this.currentUser);
  }
  
  logout() {
    this.isAuthenticated = false;
  }
}

describe('ProtectedComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ProtectedComponent],
      providers: [
        { provide: AuthService, useClass: MockAuthService }
      ]
    }).compileComponents();
  });

  it('should show content when authenticated', async () => {
    const fixture = TestBed.createComponent(ProtectedComponent);
    await fixture.whenStable();
    
    expect(fixture.nativeElement.textContent).toContain('Protected Content');
  });
});
```

## Testing User Interactions

### Click Events

```typescript theme={null}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ButtonComponent } from './button.component';

describe('ButtonComponent', () => {
  let fixture: ComponentFixture<ButtonComponent>;
  let button: HTMLButtonElement;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ButtonComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(ButtonComponent);
    button = fixture.nativeElement.querySelector('button');
  });

  it('should handle click', async () => {
    const component = fixture.componentInstance;
    spyOn(component, 'onClick');
    
    button.click();
    await fixture.whenStable();
    
    expect(component.onClick).toHaveBeenCalled();
  });

  it('should be disabled when loading', async () => {
    fixture.componentInstance.loading.set(true);
    await fixture.whenStable();
    
    expect(button.disabled).toBe(true);
  });
});
```

### Form Input

```typescript theme={null}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { InputComponent } from './input.component';

describe('InputComponent', () => {
  let fixture: ComponentFixture<InputComponent>;
  let input: HTMLInputElement;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [InputComponent, FormsModule]
    }).compileComponents();

    fixture = TestBed.createComponent(InputComponent);
    input = fixture.nativeElement.querySelector('input');
  });

  it('should update on input', async () => {
    input.value = 'test value';
    input.dispatchEvent(new Event('input'));
    await fixture.whenStable();
    
    expect(fixture.componentInstance.value()).toBe('test value');
  });
});
```

## Async Testing

### Testing Observables

```typescript theme={null}
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { of, delay } from 'rxjs';
import { AsyncService } from './async.service';

describe('AsyncService', () => {
  let service: AsyncService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(AsyncService);
  });

  it('should return data after delay', fakeAsync(() => {
    let result: string | undefined;
    
    service.getDataWithDelay().subscribe(data => {
      result = data;
    });
    
    expect(result).toBeUndefined();
    
    tick(1000); // Fast-forward time
    
    expect(result).toBe('delayed data');
  }));
});
```

### Testing Promises

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { ApiService } from './api.service';

describe('ApiService', () => {
  let service: ApiService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(ApiService);
  });

  it('should fetch data', async () => {
    const data = await service.fetchData();
    expect(data).toBeTruthy();
  });

  it('should handle errors', async () => {
    try {
      await service.fetchInvalidData();
      fail('should have thrown error');
    } catch (error) {
      expect(error).toBeTruthy();
    }
  });
});
```

## Testing Pipes

```typescript theme={null}
import { TitleCasePipe } from './title-case.pipe';

describe('TitleCasePipe', () => {
  let pipe: TitleCasePipe;

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

  it('should transform text to title case', () => {
    expect(pipe.transform('hello world')).toBe('Hello World');
  });

  it('should handle empty string', () => {
    expect(pipe.transform('')).toBe('');
  });

  it('should handle single word', () => {
    expect(pipe.transform('hello')).toBe('Hello');
  });
});
```

## Testing Directives

```typescript theme={null}
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HighlightDirective } from './highlight.directive';

@Component({
  template: `<p appHighlight>Test</p>`,
  standalone: true,
  imports: [HighlightDirective]
})
class TestComponent {}

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TestComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    element = fixture.nativeElement.querySelector('p');
  });

  it('should highlight element', async () => {
    await fixture.whenStable();
    expect(element.style.backgroundColor).toBe('yellow');
  });
});
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Act-Wait-Assert" icon="list-check">
    Follow the Act-Wait-Assert pattern for reliable async testing.
  </Card>

  <Card title="Mock Dependencies" icon="clone">
    Mock external dependencies to isolate units under test.
  </Card>

  <Card title="Test Behavior, Not Implementation" icon="bullseye">
    Focus on what components do, not how they do it.
  </Card>

  <Card title="Keep Tests Fast" icon="bolt">
    Use `fakeAsync` and `tick()` to avoid real time delays.
  </Card>
</CardGroup>

<Warning>
  Avoid testing private methods directly. Test public API and observable behavior instead.
</Warning>

## Code Coverage

Run tests with coverage reporting:

```bash theme={null}
ng test --code-coverage
```

Configure coverage thresholds in `karma.conf.js`:

```javascript theme={null}
coverageReporter: {
  type: 'html',
  dir: require('path').join(__dirname, './coverage'),
  check: {
    global: {
      statements: 80,
      branches: 80,
      functions: 80,
      lines: 80
    }
  }
}
```

## Additional Resources

* [Angular Testing Guide](https://angular.dev/guide/testing)
* [Jasmine Documentation](https://jasmine.github.io/)
* [Testing Best Practices](https://angular.dev/guide/testing/best-practices)
