Angular ships with Jasmine (assertion framework) and Karma (test runner). Use TestBed to configure and instantiate components in isolation.

Running Tests

  ng test           # Karma + browser
ng test --watch=false   # Single run (CI)
ng test --code-coverage  # Coverage report
  

New projects include a .spec.ts file alongside each component.

Testing a Service

  import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';

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

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

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return users', () => {
    const users = service.getUsers();
    expect(users.length).toBeGreaterThan(0);
    expect(users[0].name).toBeDefined();
  });

  it('should add a user', () => {
    const initialCount = service.getUsers().length;
    service.addUser({ name: 'Charlie', email: '[email protected]' });
    expect(service.getUsers().length).toBe(initialCount + 1);
  });
});
  

Testing a Component

  import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

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

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

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

  it('should display initial count', () => {
    const el = fixture.nativeElement as HTMLElement;
    expect(el.textContent).toContain('Count: 0');
  });

  it('should increment on button click', () => {
    const button = fixture.nativeElement.querySelector('button')!;
    button.click();
    fixture.detectChanges();
    expect(component.count()).toBe(1);
    expect(fixture.nativeElement.textContent).toContain('Count: 1');
  });
});
  

Mocking Dependencies

Replace real services with test doubles:

  import { of } from 'rxjs';

const mockPostService = {
  getPosts: () => of([
    { id: 1, title: 'Test Post', body: 'Body' }
  ])
};

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [PostListComponent],
    providers: [
      { provide: PostService, useValue: mockPostService }
    ]
  }).compileComponents();
});
  

Testing HTTP

Use HttpClientTestingModule to mock API calls:

  import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('PostService', () => {
  let service: PostService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [PostService]
    });
    service = TestBed.inject(PostService);
    httpMock = TestBed.inject(HttpTestingController);
  });

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

  it('should fetch posts', () => {
    service.getPosts().subscribe(posts => {
      expect(posts.length).toBe(1);
      expect(posts[0].title).toBe('Hello');
    });

    const req = httpMock.expectOne('https://api.example.com/posts');
    expect(req.request.method).toBe('GET');
    req.flush([{ id: 1, title: 'Hello', body: 'World' }]);
  });
});
  

Write tests for services and complex components first. Test behavior through the DOM, call fixture.detectChanges() after updates, and mock dependencies with useValue providers.