100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Unit Testing with Jasmine and Karma

Learn how Angular's default testing stack—Jasmine for specs and Karma for running them in a real browser—verifies component logic, services, and pipes in isolation.

Testing & DeploymentIntermediate10 min readJul 9, 2026
Analogies

Unit Testing with Jasmine and Karma

Angular's classic testing setup pairs two independent tools. Jasmine is a behavior-driven testing framework that supplies the vocabulary for writing specs: describe() blocks group related tests, it() blocks define individual expectations, and matchers like toBe(), toEqual(), and toHaveBeenCalled() assert outcomes. Karma is the test runner—it launches a real browser (or a headless one such as ChromeHeadless), injects your compiled spec files, executes them, and reports pass/fail results back to the terminal or CI pipeline. The Angular CLI wires these together automatically: ng generate creates a *.spec.ts file alongside every component, service, and pipe, and ng test boots Karma with a preconfigured karma.conf.js. Newer Angular CLI versions (17+) have shifted the default builder toward Jest or Web Test Runner for some starter projects, but Jasmine/Karma remains the long-standing default and is still widely used in production codebases and interview contexts.

🏏

Cricket analogy: Jasmine and Karma pairing is like a coaching manual (Jasmine) providing drill vocabulary — describe a session, define a drill, assert a technique is correct — while a real net session (Karma) actually runs the players through it and reports who passed, with the CLI auto-generating a scoresheet template for every new player.

Anatomy of a Spec File

A spec file mirrors the structure of the code it tests. The outer describe() names the unit under test, a beforeEach() block resets shared state before every test so tests don't leak side effects into one another, and each it() expresses a single behavioral expectation in plain language. Jasmine spies (jasmine.createSpy() or spyOn()) let you replace real dependencies with fakes, track calls, and control return values without touching the network or the DOM—critical for keeping unit tests fast and deterministic.

🏏

Cricket analogy: A describe() naming the unit under test with a beforeEach() resetting shared state is like a fresh net session where the pitch is re-rolled before every batter faces a new bowler, and a Jasmine spy replacing a real dependency is like using a bowling machine set to a known speed instead of an unpredictable live bowler.

typescript
import { CalculatorService } from './calculator.service';

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

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

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

  it('should throw when dividing by zero', () => {
    expect(() => service.divide(10, 0)).toThrowError('Division by zero');
  });

  it('should call the logger spy exactly once', () => {
    const logSpy = jasmine.createSpy('log');
    service.onResult = logSpy;
    service.add(1, 1);
    expect(logSpy).toHaveBeenCalledTimes(1);
    expect(logSpy).toHaveBeenCalledWith(2);
  });
});

Testing Services with Dependency Injection

Services that depend on other injectables are best tested through Angular's TestBed rather than by manual instantiation, because TestBed resolves the dependency graph the same way the real application does. TestBed.configureTestingModule() registers providers—often replacing a real HttpClient or a third-party SDK with a mock—and TestBed.inject() retrieves the configured instance. This keeps tests fast while still exercising Angular's DI resolution logic, catching wiring mistakes that a hand-instantiated object would never reveal.

🏏

Cricket analogy: Testing a service through TestBed instead of manual instantiation is like fielding a full training XI resolved through the actual selection process instead of hand-picking eleven random club players, catching wiring mistakes a scratch team would never reveal.

Karma requires a real browser engine, which is why CI pipelines typically install ChromeHeadless (via the Puppeteer or Karma-Chrome-Launcher packages) rather than a full desktop browser. Jest, by contrast, runs entirely in Node using jsdom to simulate the DOM, which is faster to start but doesn't execute in an actual browser rendering engine—a trade-off worth knowing when comparing Angular's default stack to tools used in React or Vue projects.

Forgetting to reset spies or shared mutable state between tests is one of the most common sources of flaky suites. Always initialize fixtures and spies inside beforeEach() rather than at the describe() level, so each test starts from a clean, predictable baseline.

Code Coverage and CI Integration

Running ng test --code-coverage produces an Istanbul-based coverage report showing which statements, branches, functions, and lines were exercised. Teams often enforce minimum coverage thresholds in karma.conf.js to prevent regressions. For continuous integration, ng test --watch=false --browsers=ChromeHeadless runs the suite once and exits with a non-zero code on failure, making it suitable for pipeline gating.

🏏

Cricket analogy: Running ng test --code-coverage for an Istanbul report is like a post-match analysis showing exactly which deliveries, shots, and fielding positions were actually tested during the innings, with teams enforcing a minimum coverage threshold like a board mandating every net session hit a fitness benchmark, and --watch=false --browsers=ChromeHeadless is like a scheduled fitness test run once with a pass/fail result for selection.

  • Jasmine provides the describe/it/expect syntax and matchers; Karma is the browser-based test runner that executes the compiled specs.
  • ng generate scaffolds a *.spec.ts file automatically for components, services, directives, and pipes.
  • Use spyOn() or jasmine.createSpy() to fake dependencies and verify interactions without hitting real network or DOM APIs.
  • TestBed.configureTestingModule() and TestBed.inject() resolve services through Angular's real dependency injection graph.
  • beforeEach() should reset fixtures and spies to avoid state leaking between tests and causing flakiness.
  • ng test --code-coverage generates an Istanbul report to track statement, branch, and line coverage over time.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#UnitTestingWithJasmineAndKarma#Unit#Testing#Jasmine#Karma#StudyNotes#SkillVeris