Angular Unit Testing with Jasmine and Karma: A Practical Guide

Quick answer

Angular unit tests are written with Jasmine (describe/it/expect), executed by the Karma runner, and wired up with TestBed, which builds a miniature Angular module for the thing under test. Run them with `ng test`, add `--code-coverage` for a report, and reach for ComponentFixture when you need to render a component and inspect its DOM.

Angular testing involves three distinct tools that are easy to conflate:

ToolRole
JasmineThe framework — describe, it, expect
KarmaThe runner — launches a browser and executes specs
TestBedAngular's testing module builder and fixture factory

The basic shape of a test

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { GreetingComponent } from './greeting.component';
 
describe('GreetingComponent', () => {
  let fixture: ComponentFixture<GreetingComponent>;
 
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [GreetingComponent],
    }).compileComponents();
 
    fixture = TestBed.createComponent(GreetingComponent);
  });
 
  it('renders the name', () => {
    fixture.componentInstance.name = 'Ada';
    fixture.detectChanges();          // required — see below
    expect(fixture.nativeElement.textContent).toContain('Ada');
  });
});

fixture.detectChanges() is the step people forget. Angular does not run change detection automatically in tests, so without it the DOM still shows the previous state and your assertion fails for a confusing reason.

Testing pipes — the easy case

A pure pipe is just a class with a transform method, so TestBed is unnecessary:

const pipe = new CapitalizePipe();
expect(pipe.transform('hello')).toBe('Hello');

That makes pipes the fastest Angular unit to test. Full walkthrough: Testing pipes in Angular with Jasmine and Karma.

Testing attribute directives — the awkward case

A directive has no template of its own, so it needs a host component to be applied to:

@Component({ template: `<div appHighlight>text</div>` })
class TestHostComponent {}

Declare both the host and the directive in TestBed, then query with By.directive(...). Full walkthrough: unit tests for Angular attribute directives.

Coverage

ng test --no-watch --code-coverage

--no-watch matters in CI: watch mode never exits and will hang the pipeline. The report lands in /coverage. See Angular unit test code coverage with Karma, and excluding files from coverage for keeping generated and mock files out of the percentage.

Enforcing a coverage floor

// karma.conf.js
coverageReporter: {
  check: {
    global: { statements: 80, branches: 70, functions: 80, lines: 80 }
  }
}

Karma exits non-zero below these thresholds, failing the build. Branch coverage is the number worth watching — full line coverage with untested conditionals still hides bugs.

Using Angular 17? See the new features guide for the control flow syntax and deferrable views.

Key takeaways

  • Jasmine is the assertion framework, Karma is the browser test runner, and TestBed is Angular's testing module builder — three separate things people often conflate.
  • Pure pipes need no TestBed at all: instantiate the class directly and call transform(), which makes them the fastest thing to test.
  • Attribute directives need a host component — declare a small test component that applies the directive, then assert on the rendered element.
  • Call fixture.detectChanges() after changing state, or the template will not reflect it and your assertion will fail confusingly.
  • `ng test --no-watch --code-coverage` is the CI form; watch mode never exits and will hang a pipeline.
  • Coverage thresholds belong in karma.conf.js under coverageReporter.check so coverage cannot silently regress.

Frequently asked questions

What is the difference between Jasmine, Karma, and TestBed?

Jasmine is the testing framework providing describe, it, and expect. Karma is the runner that launches a browser and executes the specs. TestBed is Angular's own utility for configuring a testing module and creating component fixtures. A typical Angular test uses all three together.

How do I test an Angular pipe?

For a pure pipe, skip TestBed entirely — instantiate the class and call transform() directly: const pipe = new MyPipe(); expect(pipe.transform('a')).toBe('A'). Only use TestBed when the pipe injects dependencies.

How do I test an attribute directive?

Create a small host component whose template applies the directive, declare both in TestBed, then query the element with fixture.debugElement.query(By.directive(MyDirective)) and assert on its rendered state. Directives cannot be rendered meaningfully on their own.

Why doesn't my template update in a test?

Angular does not run change detection automatically in tests. After changing component state you must call fixture.detectChanges() before querying the DOM, otherwise you assert against the previous render.

How do I generate a code coverage report?

Run ng test --no-watch --code-coverage. Angular writes an HTML report to /coverage; open coverage/index.html to browse per-file coverage. Set codeCoverage: true in angular.json to make it the default.


Related Posts