Quick Start
Run all tests with:
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:
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:
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):
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
Generate Coverage
ng test --code-coverage --watch=false
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:
HTML Report : Interactive browser-based coverage report
Text Summary : Console output showing coverage percentages
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
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):
{
"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:
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
Open Chrome DevTools
Click “DEBUG” in the Karma browser window
Set Breakpoints
Open DevTools (F12) and set breakpoints in your test or source code
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:
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:
"lint" : {
"builder" : "@angular-eslint/builder:lint" ,
"options" : {
"lintFilePatterns" : [
"src/**/*.ts" ,
"src/**/*.html"
]
}
}
CI/CD Integration
For continuous integration pipelines:
.github/workflows/test.yml
.gitlab-ci.yml
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:
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