Back to Angular Course

Angular Course

Testing is a crucial part of building reliable Angular applications. Angular supports unit testing out-of-the-box using Jasmine as the testing framework and Karma as the test runner.

Test Environment Setup

Angular CLI projects come with a pre-configured test setup using:

  • Jasmine – for writing test specs
  • Karma – for running tests in the browser
  • ChromeHeadless – headless browser for CI/CD environments

Key configuration files:

  • karma.conf.js – Karma configuration
  • test.ts – Bootstraps the Angular testing environment
  • tsconfig.spec.json – TypeScript config specific to test files

karma.conf.js

module.exports = function (config) { config.set({ frameworks: ['jasmine', '@angular-devkit/build-angular'], browsers: ['ChromeHeadless'], reporters: ['progress', 'kjhtml', 'spec'], autoWatch: true, singleRun: false, plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-coverage'), require('karma-jasmine-html-reporter'), require('karma-spec-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], coverageReporter: { dir: require('path').join(__dirname, './coverage'), reporters: [ { type: 'html' }, { type: 'text-summary' }, { type: 'lcov' } ] } }); };
  • frameworks: Jasmine and Angular test builder
  • browsers: ChromeHeadless for non-GUI test execution
  • reporters: kjhtml (browser UI), spec (console), coverage for code coverage
  • plugins: Extend Karma functionality
  • autoWatch: Watches for changes and re-runs tests
  • singleRun: Set to false for continuous testing

Bootstrap Test Environment (test.ts)

import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting, } from '@angular/platform-browser-dynamic/testing'; declare const require: { context(path: string, deep?: boolean, filter?: RegExp): { keys(): string[]; <T>(id: string): T; }; }; getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); const context = require.context('./', true, /\.spec\.ts$/); context.keys().forEach(context);
  • Initializes Angular's testing environment
  • Dynamically loads all .spec.ts test files

tsconfig.spec.json

{ "extends": "./tsconfig.json", "compilerOptions": { "module": "es2020", "types": ["jasmine", "node"], "esModuleInterop": true }, "include": ["src/**/*.spec.ts", "src/**/*.d.ts"], "exclude": ["src/test.ts", "karma.conf.js"] }
  • Ensures correct TypeScript settings for test files
  • Includes test files (.spec.ts) and typings
  • Excludes test bootstrap and Karma config from compilation

package.json

{ "name": "praveen-emp-mgmt-ui", "version": "0.0.0", "private": true, "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test --no-watch --code-coverage" }, "dependencies": { "@angular/animations": "^18.2.0", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", "@angular/forms": "^18.2.0", "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", "chart.js": "^4.4.7", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primeng": "^17.18.7", "rxjs": "~7.8.0", "tslib": "^2.3.0", "xlsx": "^0.18.5", "zone.js": "~0.14.10" }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.0", "@angular/cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0", "typescript": "~5.5.2", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "playwright": "^1.49.1" }, "jest": { "preset": "jest-preset-angular", "setupFilesAfterEnv": [ "<rootDir>/setup-jest.ts" ], "testEnvironment": "jsdom", "transform": { "^.+\\.(ts|js|html)$": "ts-jest" }, "moduleFileExtensions": [ "ts", "js", "html", "json" ], "testPathIgnorePatterns": [ "/node_modules/", "/dist/" ], "collectCoverage": true, "coverageDirectory": "coverage/jest", "globals": { "ts-jest": { "tsconfig": "tsconfig.spec.json", "stringifyContentPathRegex": "\\.html$" } } } }

Let's create test case for employee details employee-details.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmployeeDetailsComponent } from './employee-details.component'; import { of, throwError } from 'rxjs'; import { EmployeeService } from '../services/employee.service'; import { MessageService } from 'primeng/api'; import { DialogService } from 'primeng/dynamicdialog'; import * as XLSX from 'xlsx'; describe('EmployeeDetailsComponent', () => { let component: EmployeeDetailsComponent; let fixture: ComponentFixture<EmployeeDetailsComponent>; let mockEmployeeService: jasmine.SpyObj<EmployeeService>; const mockEmployees = [ { "id": 1, "name": "Praveen Oruganti", "gender": "Male", "designation": "Solution Architect", "experience": 18, "location": "Hyderabad", "salary": 55, "doj": "2020-03-15", "lastUpdatedOn": "2022-01-05" }, { "id": 2, "name": "Prasad Oruganti", "gender": "Male", "designation": "Lead Developer", "experience": 12, "location": "Chennai", "salary": 45, "doj": "2021-07-20", "lastUpdatedOn": "2022-01-06" } ]; beforeEach(async () => { mockEmployeeService = jasmine.createSpyObj('EmployeeService', ['getEmployees']); await TestBed.configureTestingModule({ imports: [EmployeeDetailsComponent], providers: [ { provide: EmployeeService, useValue: mockEmployeeService }, MessageService, DialogService ] }).compileComponents(); fixture = TestBed.createComponent(EmployeeDetailsComponent); component = fixture.componentInstance; }); it('should create the component', () => { expect(component).toBeTruthy(); }); it('should initialize and load employee data', () => { mockEmployeeService.getEmployees.and.returnValue(of(mockEmployees)); component.ngOnInit(); expect(component.employees).toEqual(mockEmployees); expect(component.columns.length).toBeGreaterThan(0); expect(mockEmployeeService.getEmployees).toHaveBeenCalled(); }); it('should handle error while fetching employees', () => { const consoleSpy = spyOn(console, 'error'); mockEmployeeService.getEmployees.and.returnValue(throwError(() => 'Error')); component.getEmployees(); expect(consoleSpy).toHaveBeenCalledWith('Error'); }); it('should filter employees by search text', () => { component.employees = [...mockEmployees]; component.searchText = 'praveen'; component.search(); expect(component.employees.length).toBe(1); expect(component.employees[0].name).toBe('Praveen Oruganti'); }); it('should reset search and fetch employees again when searchText is empty', () => { component.searchText = ''; const getEmployeesSpy = spyOn(component, 'getEmployees'); component.search(); expect(getEmployeesSpy).toHaveBeenCalled(); }); });

Let's run test command

PS D:\praveenoruganti\workspace\angular\praveen-emp-mgmt-ui> npm run test > praveen-emp-mgmt-ui@0.0.0 test > ng test --no-watch --code-coverage ✔ Browser application bundle generation complete. 14 09 2022 11:28:33.736:INFO [karma-server]: Karma v6.4.4 server started at http://localhost:9876/ 14 09 2022 11:28:33.748:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 14 09 2022 11:28:33.767:INFO [launcher]: Starting browser Chrome 14 09 2022 11:28:36.051:INFO [Chrome 137.0.0.0 (Windows 10)]: Connected on socket CWP21X2FXcv9E9buAAAB with id 57392974 Chrome 137.0.0.0 (Windows 10): Executed 5 of 5 SUCCESS (0.203 secs / 0.175 secs) TOTAL: 5 SUCCESS =============================== Coverage summary =============================== Statements : 62.06% ( 18/29 ) Branches : 50% ( 2/4 ) Functions : 63.63% ( 7/11 ) Lines : 64.28% ( 18/28 ) ================================================================================