How to Test Angular Apps with Jasmine and Karma
Set up Jasmine and Karma in an Angular project, write service and component tests with TestBed, and run them locally or in CI.
To test an Angular app with Jasmine and Karma, configure the project’s test target to use Karma, make Jasmine’s types available to TypeScript, then run ng test. Jasmine provides test suites, assertions, and spies; Karma launches the tests in a browser. Angular’s current CLI uses Vitest for new projects, but Karma remains a supported option for projects that use it already or explicitly choose it. Check your Angular version and angular.json before copying configuration.
1. Choose the runner that fits your project
For a new project that should use Karma, Angular documents this command:
ng new my-karma-app --test-runner=karma
cd my-karma-app
ng test
For a freshly generated current Angular project, expect Vitest and jsdom by default instead. In an existing project, inspect its test target in angular.json and verify the setup instructions against that project’s Angular CLI version. Angular’s testing overview and Karma and Jasmine guide describe the current options.
Karma is useful when the project already depends on its configuration, browser launchers, reporters, or plugins. Vitest is the current default for new CLI projects. Compare the project’s existing test code and browser requirements before deciding to change runners; a migration is not required merely because a new default exists.
2. Configure Karma in an existing Angular project
Angular’s documented Karma setup uses these package families. Install the versions compatible with your Angular CLI and package manager:
npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
Some Angular versions or project configurations also need a browser binary available on the machine running the tests. The launcher package connects Karma to Chrome; it does not itself guarantee that Chrome is installed or discoverable.
Configure the project’s test target to use the Angular unit-test builder with Karma. The following is the relevant shape of the target; merge it into the existing project entry rather than replacing unrelated targets or options:
{
"projects": {
"my-app": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runner": "karma"
}
}
}
}
}
}
Angular CLI versions and project layouts differ. If your project uses a different builder or target structure, follow the Karma guide for that CLI version and preserve its required options. To have the CLI generate a custom Karma configuration file when needed, Angular documents:
ng generate config karma
A hand-maintained karma.conf.js is not required for every project; CLI test-target options can construct the configuration.
In tsconfig.spec.json, include Jasmine’s global type declarations if TypeScript does not recognize describe, it, or expect:
{
"compilerOptions": {
"types": ["jasmine"]
}
}
If the file already lists other types, keep them and add jasmine to the array. Do not remove existing project-specific compiler settings.
3. Run tests locally and in CI
Run the configured test target with:
ng test
In the documented Karma workflow, this builds in watch mode, launches the browser, and reruns tests after files change. For a headless, single-run CI job, Angular’s Karma guide shows:
ng test --no-watch --no-progress --browsers=ChromeHeadless
This assumes the project has the Chrome launcher configured and CI has a compatible Chrome or Chromium installation. Browser names and flags depend on the project’s Karma configuration and CLI version. If CI cannot launch Chrome, install or expose the browser in the job environment, or configure a launcher appropriate to that environment.
When a test fails in the browser, the Karma browser window can be used to open its DEBUG tab, inspect the page with developer tools, and set breakpoints. This is useful for failures caused by rendering, event handling, or browser APIs.
4. Write a service test with Jasmine
Jasmine supplies the test structure and assertions. Angular’s TestBed supplies a testing injector so services can be created with the same dependency injection patterns used by the app.
For example, given a service like this:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class GreetingService {
greeting(name: string): string {
return `Hello, ${name}!`;
}
}
A focused unit test can retrieve it from the test injector:
import { TestBed } from '@angular/core/testing';
import { GreetingService } from './greeting.service';
describe('GreetingService', () => {
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('builds a greeting for the supplied name', () => {
const service = TestBed.inject(GreetingService);
expect(service.greeting('Ada')).toBe('Hello, Ada!');
});
});
Put shared setup in beforeEach so each case starts with a fresh testing configuration. Add providers or imports to configureTestingModule when the service depends on other tokens or services. Replace external dependencies with test doubles when the test should focus on the service’s own behavior.
5. Test component creation, rendering, and interaction
Angular component tests use TestBed to configure the testing environment and ComponentFixture to access both the component instance and rendered view. The following standalone component and test demonstrate creation, visible output, and a user interaction.
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<button type="button" (click)="count++">Add</button>
<p>Count: {{ count }}</p>
`
})
export class CounterComponent {
count = 0;
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';
describe('CounterComponent', () => {
let fixture: ComponentFixture<CounterComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CounterComponent]
});
fixture = TestBed.createComponent(CounterComponent);
fixture.detectChanges();
});
it('renders the initial count', () => {
const paragraph = fixture.nativeElement.querySelector('p');
expect(paragraph.textContent).toContain('Count: 0');
});
it('updates the rendered count after a click', () => {
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.click();
fixture.detectChanges();
expect(fixture.componentInstance.count).toBe(1);
expect(fixture.nativeElement.querySelector('p').textContent).toContain('Count: 1');
});
});
fixture.detectChanges() runs change detection so the template reflects the component state. Query the rendered DOM and assert observable behavior, such as text or enabled state, instead of testing implementation details that users cannot observe. For non-standalone components, configure the required declarations, imports, and providers according to the app’s module structure.
6. Test inputs, dependencies, and asynchronous work
For a component that depends on a service, provide a controlled test double so the test can change the result and check the rendered effect. This keeps a component test focused and avoids relying on network calls:
import { Component, inject } from '@angular/core';
import { TestBed } from '@angular/core/testing';
abstract class StatusService {
abstract status(): string;
}
@Component({
standalone: true,
template: '<p>{{ status }}</p>'
})
class StatusComponent {
private readonly statusService = inject(StatusService);
status = this.statusService.status();
}
describe('StatusComponent', () => {
it('renders the provided status', () => {
TestBed.configureTestingModule({
imports: [StatusComponent],
providers: [{ provide: StatusService, useValue: { status: () => 'Ready' } }]
});
const fixture = TestBed.createComponent(StatusComponent);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('p').textContent).toContain('Ready');
});
});
For asynchronous behavior, make the test’s wait condition explicit. Native async/await works well for promises returned by the code under test. For Angular work that must settle before asserting the view, use the fixture’s stability mechanism where appropriate:
it('renders after an asynchronous update', async () => {
const fixture = TestBed.createComponent(MyAsyncComponent);
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Loaded');
});
Replace MyAsyncComponent with the component under test and wait for the actual asynchronous work it starts. If the test controls a promise, retain and await that promise or resolve it through the test double before asserting. Legacy Zone.js helpers such as fakeAsync and tick have specific Angular and runner context; use them only when the project’s setup supports them rather than treating them as universal Jasmine functions.
Use Jasmine spies when checking calls or controlling a dependency’s return value. Keep expectations tied to the behavior that matters, such as the service result or visible response to a user action.
7. Handle failures and flaky tests
| Symptom | Likely cause | Fix |
|---|---|---|
describe, it, or expect is unknown to TypeScript |
Jasmine global types are missing from the spec TypeScript configuration. | Add jasmine to compilerOptions.types in tsconfig.spec.json, retaining any other required types. |
ng test runs Vitest or reports an unexpected runner |
The project target is configured for the current default or another runner. | Check angular.json and configure the Karma runner using instructions for the project’s Angular CLI version. |
| Karma cannot start Chrome or the browser exits immediately | Chrome is unavailable, the launcher cannot locate it, or CI lacks required browser dependencies. | Install or expose a compatible Chrome/Chromium binary and use a launcher/browser setting supported by the environment. |
| A component renders blank or stale text | Change detection has not run, an import/provider is missing, or async work has not completed. | Call fixture.detectChanges() after setup or state changes; configure required dependencies and await the operation before asserting. |
| Provider or injection error during component creation | A dependency was not included in the test configuration. | Add its provider, import the module or standalone dependency that supplies it, or provide a test double. |
| Test passes alone but fails in the suite | Shared mutable state, leaked spies, timers, or incomplete async work is affecting another case. | Build a fresh TestBed setup per case, restore spies and timers as needed, and await pending work explicitly. |
| CI never finishes | Watch mode is enabled, the browser is waiting for interaction, or an asynchronous operation remains unresolved. | Use the documented no-watch CI invocation, a headless launcher, and resolve or await all work started by the test. |
8. Performance, reliability, and cost
Jasmine and Karma are software dependencies; the relevant costs are CI time, browser setup, and maintenance of the project’s test configuration. The research sources provide no representative benchmark for Karma performance, so do not infer speed from example console output. Measure the full CI job in your own environment if runner time is a constraint.
- Keep unit tests isolated from remote services; use providers or spies to control dependencies.
- Prefer a single-run headless command in CI so jobs do not remain in watch mode.
- Use stable browser versions and explicit launcher settings to reduce environment-specific failures.
- Make asynchronous completion conditions explicit; arbitrary delays tend to make suites slower and less reliable.
- Review browser-specific tests separately from logic tests, since browser availability is an environmental requirement for Karma.
Angular’s testing utilities include APIs such as TestBed and ComponentFixture; these are Angular testing APIs, while browser launching and runner configuration belong to Karma and the CLI target.
Or skip the browser setup
If what you need is a screenshot of a running website rather than an Angular unit test, ScreenshotNeo is a website screenshot API and MCP server for developers. A single GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for parameters and formats.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, and cache hits are never billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. For Angular unit tests, keep using the test runner; use ScreenshotNeo when you need website captures without managing a browser setup.
Sign up for 1,000 free screenshots a month, with no card required.
9. Should an existing project move to Vitest?
Angular’s migration guide describes migration from Karma and Jasmine to Vitest as experimental. It requires the application build system and can involve changing dependencies and the test builder, reviewing test-target options, and replacing custom Karma configuration, plugins, reporters, and browser launchers. The schematic handles some common Jasmine patterns, but Angular says to review its changes; complex patterns may need manual work. Migration is a project choice, not a requirement for an existing Karma app.
Before migrating, inventory custom launchers, reporters, plugins, browser-only behavior, and build options. Decide whether the target environment should use DOM emulation or browser mode; Angular’s guide describes browser mode options through providers such as Playwright or WebdriverIO. Check the migration instructions for the exact Angular version, then run and review the full suite after the schematic.
See Angular’s Vitest migration guide for version-sensitive steps and limitations.
FAQ
Is Jasmine the same thing as Karma?
No. Jasmine provides the test syntax, assertions, and spies. Karma runs tests in browsers and reports their results.
Does a new Angular app use Karma by default?
Current Angular CLI projects default to Vitest with jsdom. Use the explicit Karma setup option when creating a project that should use Karma.
Do I need a Karma configuration file?
Not necessarily. Angular CLI can derive configuration from the test target. Generate a Karma config only when the project needs custom settings.
Can I test a component without launching a real browser?
The Karma workflow runs tests in a browser. Angular’s current Vitest setup uses jsdom by default, and its migration guide also describes browser mode options. Choose according to the APIs and browser behavior the test needs.


