Testing

Chapter 14
Testing — Jasmine, Karma, and TestBed
The final chapter — verifying components automatically, using the spec files Angular has generated all along

Every ng generate component has quietly created a .spec.ts file alongside each component (Chapter 1) — those are tests, and this chapter finally puts them to use. Angular's testing stack uses Jasmine (the test framework: describe/it/expect) run by Karma (the test runner, executing tests in a real browser), with TestBed as Angular's own utility for creating components in a test environment.

The concepts carry straight over from React Testing
React Advanced Chapter 4 covered testing with Vitest + React Testing Library. The vocabulary differs — Jasmine's it() for React's test(), jasmine.createSpy() for vi.fn() — but the goals are identical: render a component, simulate interaction, assert on what the user would see. If that chapter made sense, this one will feel familiar despite the different tool names.

A Test for a Service — The Simplest Case

// counter.service.spec.ts import { TestBed } from '@angular/core/testing'; import { CounterService } from './counter.service'; describe('CounterService', () => { let service: CounterService; beforeEach(() => { TestBed.configureTestingModule({}); service = TestBed.inject(CounterService); }); it('increments the count', () => { service.increment(); expect(service.getCount()).toBe(1); }); });

describe groups related tests; it defines one; beforeEach runs before each test for fresh setup; expect(...).toBe(...) asserts. TestBed.inject(CounterService) retrieves the service through the same DI system the real app uses (Chapter 6), rather than constructing it manually — so the test exercises it exactly as it'd be used in practice.

Testing a Component with TestBed

// counter.component.spec.ts import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CounterComponent } from './counter.component'; describe('CounterComponent', () => { let fixture: ComponentFixture<CounterComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [CounterComponent], // standalone components go in imports }).compileComponents(); fixture = TestBed.createComponent(CounterComponent); fixture.detectChanges(); // run initial change detection / render }); it('increments when the +1 button is clicked', () => { const button = fixture.nativeElement.querySelector('button'); button.click(); fixture.detectChanges(); // re-render after the state change const text = fixture.nativeElement.textContent; expect(text).toContain('Count: 1'); }); });

TestBed.createComponent returns a fixture — a handle to the live component plus its rendered DOM (fixture.nativeElement). The key Angular-specific detail is fixture.detectChanges(): unlike React Testing Library, which re-renders automatically, an Angular test must explicitly trigger change detection to render the initial view and to update it after a state change. Forgetting it is the most common reason an Angular test sees stale, un-updated DOM.

Mocking a Dependency

const fakeUserService = { getUsers: () => of([{ id: 1, name: 'Test User' }]), // returns a fixed Observable }; TestBed.configureTestingModule({ imports: [UserListComponent], providers: [ { provide: UserService, useValue: fakeUserService }, // swap the real service for the fake ], });

Dependency injection makes testing easier: a component asks for UserService, and the test simply provides a fake one instead via { provide: UserService, useValue: fakeUserService }. The component is none the wiser — it gets whatever DI supplies. This means a component can be tested in isolation, with a fake service returning fixed data, without ever hitting a real API — the same goal as React's mock functions (Advanced Chapter 4), achieved through DI rather than passing mocks as props.

detectChanges() is the gotcha to remember
The single most common Angular-testing mistake is asserting on the DOM without calling fixture.detectChanges() first — after creating the component (to render the initial view) and after any action that changes state (to re-render). React Testing Library handles this automatically, so it's an easy habit to miss when coming from that background. If a test sees an empty or unchanged DOM, a missing detectChanges() is the first thing to check.
Angular (Jasmine/TestBed)React (Vitest/RTL)
describe / it / expectdescribe / test / expect
TestBed.createComponentrender(...)
fixture.nativeElement.querySelectorscreen.getByRole / getByText
fixture.detectChanges()(automatic)
jasmine.createSpy()vi.fn()
{ provide, useValue } mockMock passed as a prop

Running the Tests

ng test # launches Karma, runs every .spec.ts, watches for changes

ng test runs the whole suite, opening a browser Karma controls to execute the tests in, and re-running automatically as files change — the equivalent of a test runner's watch mode. The default starter project already passes its one generated AppComponent test, so ng test works from the very first ng new.

Coding Challenges

Challenge 1

Write a spec for a CounterService (with increment/decrement/reset) using TestBed.inject, asserting the count behaves correctly after each operation.

📄 View solution
Challenge 2

Write a component spec using TestBed.createComponent that clicks the +1 button and asserts the rendered text updates — remembering detectChanges() after creation and after the click.

📄 View solution
Challenge 3

Write a spec for a component that depends on a UserService, providing a fake service (via { provide, useValue }) returning fixed data, and assert the component renders that fake data without any real HTTP call.

📄 View solution

Chapter 14 Quick Reference

  • Jasmine — the test framework (describe/it/expect/beforeEach); Karma — the runner
  • TestBed.inject(Service) — gets a service through DI for testing
  • TestBed.createComponent → a fixture (live component + nativeElement DOM)
  • fixture.detectChanges() — manually trigger render; required after creation and state changes
  • Mock a dependency with { provide: Service, useValue: fake } — DI makes isolation easy
  • ng test — runs the suite in watch mode via Karma
  • Same goals as React Testing Library; different tool names (it=test, createSpy=vi.fn)
🎉 That completes the Angular course — all 14 chapters, from the CLI and TypeScript basics through services, RxJS, signals, and testing.