How to Master Angular Component Testing: A Comprehensive Guide

Angular
Harshil Patwa April 7, 2022

Testing Angular components is essential for ensuring the reliability and functionality of your applications. In this comprehensive guide, you'll learn how to effectively test your Angular components by following a step-by-step approach. From setting up your testing environment to writing and running tests, we’ll cover all the key aspects to help you become proficient in Angular component testing.

By mastering these testing techniques, you can ensure that your components behave as expected, catch issues early, and maintain high code quality. This guide provides practical insights and hands-on examples to help you apply best practices in your testing strategy, making it easier to deliver robust and reliable Angular applications.

Angular

What is Component Testing?

Component testing involves evaluating individual components to ensure they perform correctly according to their specifications. This type of testing verifies that each component operates as intended and meets its requirements.

Component testing focuses on examining specific modules or components of a software application in isolation. In a large software system, components are often divided into smaller, manageable units, each responsible for a particular function within the overall system.

During component testing, various techniques such as unit testing, integration testing, and mocking are employed to isolate the component being tested from the rest of the system. This isolation helps identify defects or issues within the component itself, without interference from other parts of the application.

Angular offers several tools for automating the testing process, including Karma, Jasmine, Protractor, TestBed, and RouterTestingModule, which facilitate comprehensive router testing.

Prerequisites for the Angular Testing

Before diving into component testing, make sure you have the following prerequisites in place:

  1. Node.js and npm: Ensure that Node.js and npm are installed on your system. You can download and install them from the official Node.js website.
  2. Angular CLI: The Angular Command Line Interface (CLI) streamlines the setup process. Install it globally using npm with the following command:
npm install -g @angular/cli
  1. An Angular Project: Set up an Angular project using the Angular CLI. This will provide you with the necessary structure and tools for your development.
ng new my-angular-app cd my-angular-app

Angular provides built-in testing support right from the start. When you create a new Angular project using the CLI, it automatically sets up Karma and Jasmine for you. If you need to install or update these packages manually, follow the steps below:

  1. Karma: A test runner that enables you to execute your tests across multiple browsers.
npm install karma --save-dev npm install karma-cli --save-dev
  1. Jasmine: A behavior-driven development framework designed for testing JavaScript code.
npm install jasmine-core --save-dev npm install jasmine-spec-reporter --save-dev

Writing Your First Test

In this section, we will begin by writing our first unit test case. Start by creating the `HomeComponent` using the Angular CLI. This command will generate four files for you: `home.component.ts`, `home.component.css`, `home.component.spec.ts`, and `home.component.html`. Among these, the `home.component.spec.ts` file is specifically used for writing and running Angular unit tests.

//home.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `
  

title

` }) export class HomeComponent { title="welcom to homeComponent"; }

This is the HomeComponent, Let's start to test the above component.

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

import { HomeComponent } from './home.component';

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

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [ HomeComponent ]
  })
  .compileComponents();

  fixture = TestBed.createComponent(HomeComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

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

it('should render title in a p tag', () => {
  component.title="welcome to home component";
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compiled.querySelector('p').textContent).toContain('welcome to home component');
});
});

The test cases mentioned above are automatically generated for the `home.component.spec.ts` file, except for the final test case titled 'should render title in a p tag'. To test the Angular component, you'll need to use Angular testing tools like `TestBed`. `TestBed` is a utility provided by Angular that helps configure and create the testing module environment for components, services, and directives.

Declare your component and any additional components in the `declarations` array of `configureTestingModule({})`, and include any necessary dependencies in the `imports` section.

The `describe` block represents the test suite for `HomeComponent` and contains specifications and additional code used for testing the component.

The `beforeEach` function in Jasmine runs before each test suite. This block typically includes code to configure `TestBed`, set up the testing environment, and declare the component and any dependencies required for the component.

`TestBed.createComponent(HomeComponent)` is used to create an instance of the component. Various test cases are applied to this component instance using matcher functions to determine if the tests pass or fail.

`detectChanges()` triggers change detection in the component. When properties in a component change and need to be reflected in the template, Angular uses change detection to update the view.

The `it()` function is used to write individual test cases. It takes a description and a callback function as arguments. In the first `it` block, we check whether the component is successfully created.

In the second test case, we verify that the title property is correctly rendered in the template. The `detectChanges()` method binds the data to the component instance. Using `fixture.debugElement.nativeElement`, we can check if the compiled component code includes a `

` HTML element with the text "welcome to home component".

The `ng test` command is used in Angular applications to run unit tests for your project. Executing `ng test` triggers Angular's test runner, typically Karma, to execute the unit tests defined in your project.

DOM Manipulation and Interaction

In this section, we will explore how to test DOM elements. Start by creating a component with a button, and then verify the results after clicking the button. The goal is to test whether the component's properties are updated correctly when the button is clicked.

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

@Component({
selector: 'app-home',
template: `
  
  

firstname

` }) export class HomeComponent { firstname = "Dhanashri"; constructor(){ } changeFirstName() { this.firstname = "Bhagyashri"; } }

In the code snippet above, we have created a `HomeComponent` that contains a button. Clicking this button will update the `firstname` property of the component. Let's proceed with testing the component to ensure it behaves as expected.

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { HomeComponent } from './home.component';

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

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [ HomeComponent ]
  })
  .compileComponents();
});

beforeEach(() => {
  fixture = TestBed.createComponent(HomeComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

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

it('it should display firstname initially', () => {
  const firstNameElement: HTMLElement = 	     		fixture.nativeElement.querySelector('.fname');
  expect(firstNameElement.textContent).toContain('Dhanashri');
});

it('should update firstname on button click', () => {
  const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
  button.click();
  fixture.detectChanges();
  const firstNameElement: HTMLElement = fixture.nativeElement.querySelector('.fname');
  expect(firstNameElement.textContent).toContain('Bhagyashri');
});
}); 

In this test suite, we first create the component fixture and instance to access the component's DOM elements and properties. The initial test case verifies that the component is successfully created.

The second test ensures that the `firstname` property is displayed correctly on the template when the component is initially loaded.

Finally, the last test checks that clicking the button updates the `firstname` property correctly. The `fixture.detectChanges()` method is used to handle change detection in this process.

Test component with routing

Routing enables navigation from one component to another within an application. In this section, we will test whether the component correctly navigates to the intended route based on specific actions or conditions.

The `RouterTestingModule`, available in the `@angular/router/testing` package, is used to create a testing environment for Angular's routing functionality. This module allows you to set up routing tests effectively.

To test routing in Angular, create a component with a button that triggers navigation to a different route upon being clicked. Utilize the `Router` class from `@angular/router` for handling route transitions between different components.

//home.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
selector: 'app-home',
template: `
  
`
})
export class HomeComponent {
constructor(private router: Router) {}

navigate() {
  this.router.navigate(['/customerList']);
}
}

In this component, clicking the button triggers navigation to the 'customerList' route.

The next step is to test the routing functionality within the component to ensure it directs to the correct route as expected.

//my.component.spec.ts
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';
import { HomeComponent } from './home.component';

describe('HomeComponent ', () => {
let component: HomeComponent ;
let fixture: ComponentFixture;
let router: Router;

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [ HomeComponent  ],
    imports: [ RouterTestingModule ]
  })
  .compileComponents();
});

beforeEach(() => {
  fixture = TestBed.createComponent(HomeComponent );
  component = fixture.componentInstance;
  router = TestBed.inject(Router);
  fixture.detectChanges();
});

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

it('should navigate to other component after button clicking', () => {
  const navSpy = spyOn(router, 'navigate');
  const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
  button.click();
  expect(navSpy).toHaveBeenCalledWith(['/customerList']);
});
});    

In the provided code snippets, the `RouterTestingModule` is utilized to offer mock routing capabilities for testing purposes. To verify router navigation, we inject the `Router` service into the component and use `spyOn` to monitor its `navigate` method.

Within the test suite, we ensure that clicking the button successfully navigates to the specified or expected route by checking the behavior of the `navigate` method.

Testing Component Inputs and Output

In this section, we'll explore how to test the `@Input` and `@Output` decorators in Angular. These decorators facilitate communication between components: `@Input` allows a component to receive data, while `@Output` is used to emit custom events to a parent component.

To test components using these decorators, we need to ensure that input properties are correctly bound and that events emitted by the component are properly handled. Start by creating a component that utilizes both `@Input` and `@Output`. The `@Input` decorator will accept values, while the `@Output` decorator will emit events to the parent component.

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
selector: 'app-child',
template: `
  

firstname

`, }) export class ChildComponent { @Input() firstname: string = ''; @Output() updateFirstnameEvent: EventEmitter = new EventEmitter(); updateFirstName() { const newName = 'Bhagyashri'; this.updateFirstnameEvent.emit(newName); } }

In the provided code snippet, we have defined a `ChildComponent` class with an `@Input` property named `firstname` and an `EventEmitter` that emits a new name to the parent component. Next, we will test whether the `@Input` and `@Output` decorators function as intended.

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChildComponent } from './child.component';

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

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [ChildComponent]
  })
    .compileComponents();
});

beforeEach(() => {
  fixture = TestBed.createComponent(ChildComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

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

it('should display the firstname when first time component load', () => {
  
  const firstname = 'Dhanashri';
  component.firstname = firstname;

  fixture.detectChanges();

  const firstnameElement = fixture.nativeElement.querySelector('p');
  expect(firstnameElement.textContent).toContain(firstname);
});

it('should emit a new firstname when update firstname button is clicked', () => {
  const newName = 'Bhagyashri';
  spyOn(component.updateFirstnameEvent, 'emit');

  const button = fixture.nativeElement.querySelector('button');
  button.click();

  expect(component.updateFirstnameEvent.emit).toHaveBeenCalledWith(newName);
});
});

In the above test cases, the `fixture.detectChanges()` method is used to trigger change detection. This ensures that changes to input properties and emitted output events are processed correctly.

The line `spyOn(component.updateFirstnameEvent, 'emit')` creates a spy on the `emit` method of the `updateFirstnameEvent` EventEmitter in the component. This allows you to monitor and verify whether the `emit` method is called and what arguments it is called with.

In the test, the `toHaveBeenCalledWith()` matcher method is employed to check that the custom event emits the expected message with the correct value.

Conclusion

Component testing is a type of software testing that focuses on evaluating individual components or modules of a software application in isolation. This process ensures that each component functions as expected.

Angular offers a range of tools for testing, such as TestBed, which sets up the testing environment, and RouterTestingModule, which configures the environment for testing Angular's routing infrastructure. These tools verify that components navigate to the correct routes based on specific actions or conditions.

By mastering these component testing techniques, you can enhance the quality, reliability, and maintainability of your Angular applications through thorough testing of individual components.

__

Ready to Transform Your eCommerce?

Whether you're launching something new or transforming an existing venture, Mavenbird is here to help bring your ideas to life.

Loading...

Talk to an Expert

Request a Free Quote and expert consultation.