Skip to main content

Quick Start

Run all tests with:
npm test
This command runs ng test and starts the Karma test runner.

Test Framework

The project uses the following testing stack:

Jasmine

v5.1.0 - Testing framework

Karma

v6.4.0 - Test runner

Chrome Launcher

Runs tests in Chrome browser

Coverage Reports

HTML and text summary reports

Karma Configuration

The test environment is configured in karma.conf.js:
karma.conf.js
module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      jasmine: {
        // Configuration options for Jasmine
        // Can disable random execution with `random: false`
        // or set a specific seed with `seed: 4321`
      },
      clearContext: false // leave Jasmine Spec Runner output visible
    },
    jasmineHtmlReporter: {
      suppressAll: true // removes duplicated traces
    },
    coverageReporter: {
      dir: require('path').join(__dirname, './coverage/app'),
      subdir: '.',
      reporters: [
        { type: 'html' },
        { type: 'text-summary' }
      ]
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    restartOnFileChange: true
  });
};

Running Tests

Interactive Mode (Default)

Run tests with watch mode and live browser preview:
npm test
Tests run in Chrome by default and automatically rerun when files change. The Jasmine HTML reporter displays results in the browser.

Single Run Mode

Run tests once and exit (useful for CI/CD):
ng test --watch=false

Headless Mode

Run tests in headless Chrome:
ng test --browsers=ChromeHeadless --watch=false

CI Configuration

For continuous integration environments:
ng test --configuration ci
The CI configuration (angular.json):
  • Disables progress output for cleaner logs
  • Runs without watch mode

Code Coverage

Generate Coverage Reports

Run tests with coverage:
ng test --code-coverage --watch=false
Coverage reports are generated in the coverage/app/ directory.

View Coverage

1

Generate Coverage

ng test --code-coverage --watch=false
2

Open HTML Report

open coverage/app/index.html
# or on Linux
xdg-open coverage/app/index.html

Coverage Configuration

The coverage reporter generates two types of reports:
  1. HTML Report: Interactive browser-based coverage report
  2. Text Summary: Console output showing coverage percentages
karma.conf.js (excerpt)
coverageReporter: {
  dir: require('path').join(__dirname, './coverage/app'),
  subdir: '.',
  reporters: [
    { type: 'html' },
    { type: 'text-summary' }
  ]
}

Test File Structure

Test files follow Angular conventions:
src/
├── app/
│   ├── components/
│   │   ├── menu-item.component.ts
│   │   ├── menu-item.component.spec.ts  ← Test file
│   ├── services/
│   │   ├── order.service.ts
│   │   ├── order.service.spec.ts        ← Test file
│   └── app.component.spec.ts
└── test.ts                               ← Test entry point
Test files are named with .spec.ts suffix and located next to the files they test.

Writing Tests

Component Test Example

menu-item.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuItemComponent } from './menu-item.component';

describe('MenuItemComponent', () => {
  let component: MenuItemComponent;
  let fixture: ComponentFixture<MenuItemComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MenuItemComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(MenuItemComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should display item name', () => {
    component.item = { name: 'California Roll', price: 8.99 };
    fixture.detectChanges();
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('.item-name')?.textContent)
      .toContain('California Roll');
  });
});

Service Test Example

order.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { OrderService } from './order.service';

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

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(OrderService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should add item to cart', () => {
    const item = { id: 1, name: 'Sushi', price: 10 };
    service.addToCart(item);
    expect(service.getCartItems()).toContain(item);
  });

  it('should calculate total price', () => {
    service.addToCart({ id: 1, name: 'Roll', price: 8.99 });
    service.addToCart({ id: 2, name: 'Sashimi', price: 12.99 });
    expect(service.getTotalPrice()).toBe(21.98);
  });
});

Test Configuration Files

TypeScript Configuration

Tests use a dedicated TypeScript config (tsconfig.spec.json):
tsconfig.spec.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "types": [
      "jasmine"
    ]
  },
  "files": [
    "src/test.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}

Test Entry Point

The src/test.ts file initializes the test environment:
test.ts
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

// Initialize Angular testing environment
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);

Advanced Testing

Custom Karma Configuration

You can customize Jasmine behavior in karma.conf.js:
client: {
  jasmine: {
    random: false,        // Disable random test order
    seed: 12345,          // Use fixed seed for reproducibility
    stopSpecOnExpectationFailure: false,
    stopOnSpecFailure: false
  }
}

Debugging Tests

1

Start Test Runner

npm test
2

Open Chrome DevTools

Click “DEBUG” in the Karma browser window
3

Set Breakpoints

Open DevTools (F12) and set breakpoints in your test or source code
4

Refresh to Rerun

Refresh the page to rerun tests with breakpoints active

Run Specific Tests

Use Jasmine’s fdescribe and fit to focus on specific tests:
// Run only this suite
fdescribe('OrderService', () => {
  // ...
});

// Run only this test
fit('should calculate total', () => {
  // ...
});
Remember to remove fdescribe and fit before committing. They’re for local debugging only.

Skip Tests

Use xdescribe and xit to temporarily skip tests:
// Skip this suite
xdescribe('PaymentService', () => {
  // ...
});

// Skip this test
xit('should process payment', () => {
  // ...
});

Linting

Run ESLint to check code quality:
npm run lint
The project uses:
  • ESLint: v9.16.0 with Angular-specific rules
  • TypeScript ESLint: v8.18.0 for TypeScript linting

Lint Configuration

Lint rules are configured in angular.json:
angular.json (excerpt)
"lint": {
  "builder": "@angular-eslint/builder:lint",
  "options": {
    "lintFilePatterns": [
      "src/**/*.ts",
      "src/**/*.html"
    ]
  }
}

CI/CD Integration

For continuous integration pipelines:
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --watch=false --code-coverage
      - run: npm run build -- --configuration production

Troubleshooting

Install Chrome or use ChromeHeadless:
ng test --browsers=ChromeHeadless
For CI environments, ensure Chrome is installed:
# Ubuntu/Debian
apt-get install chromium-browser
Increase the Jasmine timeout in karma.conf.js:
client: {
  jasmine: {
    timeoutInterval: 10000  // 10 seconds
  }
}
Increase Node.js memory for large test suites:
NODE_OPTIONS=--max_old_space_size=4096 npm test
Karma uses port 9876 by default. Change it in karma.conf.js:
port: 9877,

Best Practices

Test Coverage

Aim for at least 80% code coverage for critical business logic

Test Isolation

Each test should be independent and not rely on test execution order

Mock Dependencies

Use TestBed to provide mock services and dependencies

Test Behavior

Focus on testing behavior and user interactions, not implementation details

Next Steps

Building

Create production builds for deployment

Architecture

Understand the app architecture

Build docs developers (and LLMs) love