Menu

Testing Angular Applications

Testing Angular Applications

Unit Testing with Jasmine and Karma

Angular ships with Jasmine (the test framework) and Karma (the test runner) out of the box. Unit tests live in .spec.ts files next to the code they test.

Testing a service:

// course.service.spec.ts

import { TestBed } from '@angular/core/testing';

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { CourseService } from './course.service';

describe('CourseService', () => {

let service: CourseService;

let httpMock: HttpTestingController;

beforeEach(() => {

TestBed.configureTestingModule({

imports: [HttpClientTestingModule],

providers: [CourseService]

});

service = TestBed.inject(CourseService);

httpMock = TestBed.inject(HttpTestingController);

});

afterEach(() => httpMock.verify()); // ensure no unexpected requests

it('should fetch courses', () => {

const mockCourses = [{ id: 1, name: 'Angular', level: 'Beginner' }];

service.getCourses().subscribe(courses => {

expect(courses.length).toBe(1);

expect(courses[0].name).toBe('Angular');

});

const req = httpMock.expectOne('https://api.example.com/courses');

expect(req.request.method).toBe('GET');

req.flush(mockCourses); // simulate the API response

});

});

Testing a component:

// course-list.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CourseListComponent } from './course-list.component';

import { CourseService } from './course.service';

import { of } from 'rxjs';

describe('CourseListComponent', () => {

let component: CourseListComponent;

let fixture: ComponentFixture<CourseListComponent>;

let mockCourseService: jasmine.SpyObj<CourseService>;

beforeEach(() => {

mockCourseService = jasmine.createSpyObj('CourseService', ['getCourses']);

mockCourseService.getCourses.and.returnValue(of([

{ id: 1, name: 'Angular', level: 'Beginner' }

]));

TestBed.configureTestingModule({

declarations: [CourseListComponent],

providers: [{ provide: CourseService, useValue: mockCourseService }]

});

fixture = TestBed.createComponent(CourseListComponent);

component = fixture.componentInstance;

fixture.detectChanges();

});

it('should create the component', () => {

expect(component).toBeTruthy();

});

it('should display courses', () => {

const listItems = fixture.nativeElement.querySelectorAll('li');

expect(listItems.length).toBe(1);

expect(listItems[0].textContent).toContain('Angular');

});

});

End-to-End Testing with Cypress

Unit tests check individual pieces. E2E tests check your entire app from the user's perspective — clicking buttons, filling forms, and verifying what appears on screen.

Installing Cypress:

npm install cypress --save-dev

npx cypress open

Writing a basic E2E test:

// cypress/e2e/courses.cy.js

describe('Course List Page', () => {

beforeEach(() => {

cy.visit('/courses');

});

it('should display the course list', () => {

cy.get('h2').should('contain', 'Courses');

cy.get('mat-card').should('have.length.greaterThan', 0);

});

it('should filter courses when searching', () => {

cy.get('input[placeholder="Search courses..."]').type('Angular');

cy.get('mat-card').should('have.length', 1);

cy.get('mat-card-title').should('contain', 'Angular');

});

it('should navigate to course detail', () => {

cy.get('mat-card').first().click();

cy.url().should('include', '/courses/');

});

});

Testing NgRx Store

Testing NgRx requires verifying that actions trigger the right state changes through the reducer.

// course.reducer.spec.ts

import { courseReducer, initialState } from './course.reducer';

import { loadCourses, loadCoursesSuccess, loadCoursesFailure } from './course.actions';

describe('Course Reducer', () => {

it('should set loading to true on loadCourses', () => {

const state = courseReducer(initialState, loadCourses());

expect(state.loading).toBe(true);

expect(state.error).toBeNull();

});

it('should populate courses on loadCoursesSuccess', () => {

const courses = [{ id: 1, name: 'Angular', level: 'Beginner', description: '' }];

const state = courseReducer(initialState, loadCoursesSuccess({ courses }));

expect(state.courses.length).toBe(1);

expect(state.loading).toBe(false);

});

it('should set error on loadCoursesFailure', () => {

const state = courseReducer(initialState, loadCoursesFailure({ error: 'Network error' }));

expect(state.error).toBe('Network error');

expect(state.loading).toBe(false);

});

});