← Back to Tutorials Chapter 13

Testing & Patterns

Testing is a critical part of building reliable Angular applications. This chapter covers unit testing with Jasmine and Karma, along with common design patterns used throughout Angular.

Unit Testing with Jasmine & Karma

Angular CLI scaffolds Jasmine test files alongside your components, services, and other artifacts. Karma is the test runner that executes these tests in a browser environment.

ng test

This command starts Karma, opens a browser, runs all *.spec.ts files, and reports results.

TestBed

TestBed is the primary API for configuring Angular testing modules. Use it to create a testing environment that mimics your application module.

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [MyComponent],
    imports: [HttpClientTestingModule],
    providers: [MyService]
  }).compileComponents();
});

ComponentFixture & detectChanges

A ComponentFixture wraps the component for testing. Call detectChanges() to trigger change detection.

it('should display the title', () => {
  const fixture = TestBed.createComponent(MyComponent);
  fixture.detectChanges();
  const compiled = fixture.nativeElement as HTMLElement;
  expect(compiled.querySelector('h1')?.textContent).toContain('Welcome');
});

debugElement

debugElement provides a type-safe way to query the component template and inspect its properties.

it('should have a submit button', () => {
  const fixture = TestBed.createComponent(MyComponent);
  const debugEl = fixture.debugElement;
  const btn = debugEl.query(By.css('button[type="submit"]'));
  expect(btn).toBeTruthy();
});

Testing Services

Use HttpClientTestingModule and HttpTestingController to test HTTP-based services without making real network requests.

let httpTestingController: HttpTestingController;
let service: DataService;

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

it('should return expected data', () => {
  const testData = { id: 1, name: 'Test' };
  service.getData().subscribe(data => {
    expect(data).toEqual(testData);
  });
  const req = httpTestingController.expectOne('/api/data');
  req.flush(testData);
});

Lazy Loading with loadChildren

Lazy loading delays module loading until the user navigates to that route, improving initial bundle size.

const routes: Routes = [
  { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) }
];

Singleton Pattern (providedIn: 'root')

Marking a service with providedIn: 'root' ensures only one instance exists across the entire application — the singleton pattern.

@Injectable({ providedIn: 'root' })
export class SharedService {
  private data = new BehaviorSubject<string>('initial');
  data$ = this.data.asObservable();

  updateData(value: string) {
    this.data.next(value);
  }
}

Observer Pattern (Subject, BehaviorSubject)

RxJS Subjects implement the observer pattern. Subject multicasts values to multiple subscribers. BehaviorSubject also emits the current value to new subscribers.

const subject = new Subject<number>();
subject.subscribe(v => console.log('A:', v));
subject.next(1);
subject.next(2);

const behaviorSubject = new BehaviorSubject<number>(0);
behaviorSubject.subscribe(v => console.log('B:', v));
behaviorSubject.next(1);
Use BehaviorSubject over Subject when new subscribers need to know the latest value immediately.
Angular testing architecture
Practice Task: Write a unit test for a service that fetches user data from /api/users. Mock the HTTP call using HttpTestingController. Then write a component test that verifies the user list renders correctly in the template.