Friday, 18 April 2025

Angular 19 | Chapter 12 | Unit Testing Angular Application

Unit testing is crucial to ensure that your Angular application is robust, maintainable, and behaves as expected. Angular provides powerful tools to facilitate testing, including Jasmine (for writing tests), Karma (for running tests), and TestBed (for configuring test environments).

Here’s how you can approach unit testing Angular applications.

 

🚀 Setting Up Unit Testing in Angular

When you generate a new Angular project using ng new, Angular comes pre-configured with Jasmine for testing and Karma as the test runner.

  • Jasmine: A behavior-driven testing framework for JavaScript.
  • Karma: A test runner that runs Jasmine tests in the browser.
  • TestBed: Angular’s primary API for configuring and initializing Angular components and services in tests.

 

🧪 1. Unit Testing Services

Example: Testing a Service
 

task.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  constructor() {}

getTasks() {
    return ['Task 1', 'Task 2', 'Task 3'];
  }
}
 

task.service.spec.ts (Test File)

import { TestBed } from '@angular/core/testing';
import { TaskService } from './task.service';

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

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

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

it('should return tasks', () => {
    const tasks = service.getTasks();
    expect(tasks.length).toBe(3);
    expect(tasks).toEqual(['Task 1', 'Task 2', 'Task 3']);
  });
});
 

Breakdown:

  • TestBed: Angular's testing environment to configure and initialize services.
  • beforeEach: Setup function to initialize the service before each test.
  • expect: Jasmine’s assertion library used to test expectations.

 

🧪 2. Unit Testing Components

Example: Testing a Component
 

task-list.component.ts
 

import { Component } from '@angular/core';
import { TaskService } from './task.service';

@Component({
  selector: 'app-task-list',
  template: '<ul><li *ngFor="let task of tasks">{{task}}</li></ul>'
})
export class TaskListComponent {
  tasks: string[] = [];

constructor(private taskService: TaskService) {}

ngOnInit() {
    this.tasks = this.taskService.getTasks();
  }
}
 

task-list.component.spec.ts (Test File)
 

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskListComponent } from './task-list.component';
import { TaskService } from './task.service';
import { of } from 'rxjs';

class MockTaskService {
  getTasks() {
    return ['Task 1', 'Task 2', 'Task 3'];
  }
}

describe('TaskListComponent', () => {
  let component: TaskListComponent;
  let fixture: ComponentFixture<TaskListComponent>;
  let mockTaskService: MockTaskService;

beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TaskListComponent],
      providers: [{ provide: TaskService, useClass: MockTaskService }]
    });

fixture = TestBed.createComponent(TaskListComponent);
    component = fixture.componentInstance;
    mockTaskService = TestBed.inject(TaskService);
  });

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

it('should load tasks on init', () => {
    fixture.detectChanges();
    expect(component.tasks.length).toBe(3);
    expect(component.tasks).toEqual(['Task 1', 'Task 2', 'Task 3']);
  });
});
 

Key Concepts:

  • ComponentFixture: Used to test and interact with the component’s DOM.
  • Mocking services: Using a mock service to isolate the component's behavior.
  • detectChanges: Tells Angular to run change detection so that the component reflects its updated state.

 

🧪 3. Testing Forms

Example: Testing a Form Component

 

user-form.component.ts

 

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-user-form',
  template: `<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
               <input formControlName="name" />
               <button type="submit" [disabled]="userForm.invalid">Submit</button>
             </form>`
})
export class UserFormComponent {
  userForm: FormGroup;

constructor(private fb: FormBuilder) {
    this.userForm = this.fb.group({
      name: ['', Validators.required]
    });
  }

onSubmit() {
    console.log(this.userForm.value);
  }
}

 

user-form.component.spec.ts (Test File)

 

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { UserFormComponent } from './user-form.component';

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

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      declarations: [UserFormComponent]
    });

fixture = TestBed.createComponent(UserFormComponent);
    component = fixture.componentInstance;
  });

it('should create the form with one control', () => {
    expect(component.userForm.contains('name')).toBeTrue();
  });

it('should make the form invalid if the name is empty', () => {
    const nameControl = component.userForm.get('name');
    nameControl?.setValue('');
    expect(component.userForm.invalid).toBeTrue();
  });

it('should submit the form', () => {
    const nameControl = component.userForm.get('name');
    nameControl?.setValue('John Doe');
    fixture.detectChanges();
    component.onSubmit();
    expect(component.userForm.valid).toBeTrue();
  });
});

 

Key Concepts:

  • ReactiveFormsModule: Importing this module to use reactive forms.
  • Form validation: Checking if the form is valid or invalid.
  • Form control value: Testing form control values and their validations.

 

🧪 4. Testing HTTP Requests

To test HTTP requests, you can use HttpClientTestingModule and HttpTestingController.

 

Example: Testing HTTP Service

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { MyService } from './my-service';

describe('MyService', () => {
  let service: MyService;
  let httpMock: HttpTestingController;

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [MyService]
    });
    service = TestBed.inject(MyService);
    httpMock = TestBed.inject(HttpTestingController);
  });

it('should fetch data', () => {
    const mockData = [{ id: 1, name: 'Item 1' }];
   
    service.getData().subscribe((data) => {
      expect(data).toEqual(mockData);
    });

const req = httpMock.expectOne('https://api.example.com/data');
    expect(req.request.method).toBe('GET');
    req.flush(mockData);
  });

afterEach(() => {
    httpMock.verify();
  });
});

 

🧠 Summary

  • Unit Testing Services: Use TestBed to configure and inject services, test their methods.
  • Unit Testing Components: Test components with ComponentFixture, mock dependencies, and test DOM and behaviors.
  • Form Testing: Use ReactiveFormsModule for reactive form testing and validate form controls.
HTTP Testing: Use HttpClientTestingModule and HttpTestingController to mock and test HTTP requests.

No comments:

Post a Comment