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);
});
});











