Mastering Angular Component Testing: A Step-by-Step Guide

    Thursday, June 20, 20248 min read1205 views
    Mastering Angular Component Testing: A Step-by-Step Guide

    What is Component Testing?

    Component testing involves testing individual components individually to ensure they function correctly according to their requirements. It verifies that each component operates as expected and meets its requirements. it is called component testing.

    Component testing focuses on testing individual modules or components of a software application in isolation. In software applications, a large system is often broken down into smaller, more manageable units called components or modules. Each individual component performs a specific function within the system.

    During component testing, testers typically use a variety of techniques such as unit testing, integration testing, and mocking to isolate the component being tested from the rest of the system. This isolation helps the testing team identify any defects or issues within the component itself without interference from other parts of the software.

    Angular provides several tools for automated testing processes like Karma, Jasmine, Protractor, TestBed, and RouterTestingModule to perform router testing.

    Prerequisites for the Angular Testing

    Before going deep into component testing, ensure you have the following prerequisites:

    1. Node.js and npm: verify that npm and Node.js are installed. You can download and install them from the official Node.js website

    2. Angular CLI: the setup process is simplified by the Angular Command Line Interface (CLI). Install it globally using npm:

    npm install -g @angular/cli

    3. An Angular Project: create an angular project using the Angular CLI

    ng new my-angular-app cd my-angular-app

    Installing Necessary Packages (Karma, Jasmine, Angular Testing Utilities)
    Angular comes with testing support that is out of the box. Creating a new Angular project using the CLI sets up Karma and Jasmine for you. However, if you need to install or update these packages manually, you can follow these steps:

    1. Karma: A test runner that allows you to execute your tests in various browsers.

    npm install karma --save-dev npm install karma-cli --save-dev

    2. Jasmine: A behavior-driven development framework 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 start writing our first unit test case. First, create the component HomeComponent using angular CLI. it will generate four files for you. Those four files include home.component.ts, home.component.css, home.component.spec.ts, and home.component.html. From these four files, home.component.spec.ts is used for the angular unit testing above.

    //home.component.ts
    import { Component } from '@angular/core';
    @Component({
      selector: 'app-home',
      template: `
        <p>{{ title }}</p>`
      })
    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<HomeComponent>;
    
      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');
      });
    });

    Above mentioned test cases are automatically generated for home.component.spec.ts. except the last test case ('should render title in a p tag'). To test the angular component we are required to use some angular testing tools like TestBed. TestBed is one utility provided by angular that is used to configure and create the test module environment for our component, services, and directive.

    Declare your component and other components in the declaration array of configureTestingModule({}). whatever dependencies are required for testing vs component import in the import section.

    The  describe code block represents the test suite for  HomeComponent. It contains specs and additional units of code that’s used for testing  HomeComponent.

    beforeEach is one function in Jasmine that runs before each test suite. this block contains the code like configure the testBed for creating a testing environment and declares the component and dependency that is used in the component.

    TestBed.createComponent(HomeComponent) is used to create the instance of the component. to test the various test cases in our component we used the matcher function on the component instance which shows that our test fails all expectations are pass or fail.

    detectChanges() is used to trigger the change detection in the component. whenever any property changes in a component and we want to reflect it on the template at that time angular triggers the change detection.

    it() function is used to write test cases. it accepts one message and callback function as an argument. In our first it block, we check whether the component is created or not.

    In the second test case, we verify that the title property should be rendered on the template correctly. The spec introduces the  detectChanges() method, which binds the data to the component instance. Then, using the  fixture.debugElement.nativeElement property, we can check if the compiled component code contains a p HTML element with text that reads “welcome to home component”.

    The ng test command is used in Angular applications to run unit tests for your project. When you execute the ng test, Angular's test runner, usually Karma, is invoked to execute the unit tests defined in your project.

    Want to have a strong foundation for your next Angular development project?
    Hire Angular Developer from Angular Minds to get code organization and maintainability for complex applications.

    DOM Manipulation and Interaction

    In this section, we are going to discuss how to test the DOM elements. First, create a component that includes one button and test results after clicking on that button it will change some properties in the component. In this section, we will test whether the property updates correctly or not when we click on the button.

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-home',
      template: `
        <button (click)="changeFirstName()">Update FirstName</button>
        <p class="fname">{{ firstname }}</p>
      `
    })
    export class HomeComponent {
      firstname = "Dhanashri";
      constructor(){
      }
      changeFirstName() {
        this.firstname = "Bhagyashri";
      }
    }

    In the above code snippet, we have created a component HomeComponent that includes one button. after clicking on the button it will change the firstname property of the component. let's start to test the component.

    import { TestBed, ComponentFixture } from '@angular/core/testing';
    import { HomeComponent } from './home.component';
    
    describe('HomeComponent', () => {
      let component: HomeComponent;
      let fixture: ComponentFixture<HomeComponent>;
    
      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, first, we create the component fixture and component instance to access the component's DOM element and properties. Then in the First test case, we test that the component is created successfully.

    In the second test, We checked that initially when the component was loaded firstname property displayed correctly on the template.

    In the last test, we checked that after clicking on the button it would update the firstname property correctly. fixture.detectChanges() is used for change detection purposes.

    Test component with routing

    Routing helps to move or navigate from one component to another component. In this section, we will verify that the component navigates to the correct route when certain actions or conditions are encountered.

    The RouterTestingModule is one module provided by Angular and is a module testing is present in the @angular/router/testing package. This module lets you set up a testing environment for Angular's routing infrastructure.

    To perform the route testing process in angular, create one component that includes one button, and after clicking on that it will navigate to another route. use the Router class from '@angular/router' which is used for navigating from one route to another route.

    //home.component.ts
    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    
    @Component({
      selector: 'app-home',
      template: `
        <button (click)="navigate()">navigate to another component</button>
      `
    })
    export class HomeComponent {
      constructor(private router: Router) {}
    
      navigate() {
        this.router.navigate(['/customerList']);
      }
    }

    In this component, when the button is clicked, it navigates to the 'customerList' route.

    The next step is to test the routing in the component.

    //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<HomeComponent >;
      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 above code snippets, RouterTestingModule is used to provide mock router functionality for testing. To test the router navigation we inject the Router service into the component and then spyOn its navigate method. in the test suite, we check that after clicking on the button it should move to the given route or expected route.

    Testing Component Inputs and Outputs

    In this section, we are going to discuss how to test the @Input and @Output decorator. @Input and @Output decorators are used to establish the communication between two components. @Input decorator is used to send the data to the component and @Output decorator is used to emit the custom event from the component.

    Testing components that use these decorators involves ensuring that the input properties are correctly bound and that events emitted by the component are handled appropriately.

    Create one component that includes @input and @output decorator. @input decorator accepts the input values and @Output decorator emits a particular event to the parent component.

    import { Component, EventEmitter, Input, Output } from '@angular/core';
    
    @Component({
      selector: 'app-child',
      template: `
        <p>{{ firstname }}</p>
        <button (click)="updateFirstName()">update firstname</button>
      `,
    })
    export class ChildComponent {
      @Input() firstname: string = '';
      @Output() updateFirstnameEvent: EventEmitter<string> = new EventEmitter<string>();
    
      updateFirstName() {
        const newName = 'Bhagyashri';
        this.updateFirstnameEvent.emit(newName);
      }
    }

    In the above code snippet, we have created a ChildComponent class that includes firstname as an input property and one EventEmmitor that emits the new name to the parent component. Lets start the test @input and @output decorator work as expected or not.

    import { ComponentFixture, TestBed } from '@angular/core/testing';
    import { ChildComponent } from './child.component';
    
    describe('ChildComponent', () => {
      let component: ChildComponent;
      let fixture: ComponentFixture<ChildComponent>;
    
      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, fixture.detectChanges() is use to trigger the change detection. change detection trigger after input properties are set and after emitting output events.

    The line spyOn(component.updateFirstnameEvent, 'emit'); sets up a spy on the emit method of the updateFirstnameEvent EventEmitter in the component. This allows you to track and assert whether the emit method has been called and with what arguments.

    In this test toHaveBeenCalledWith() matcher method is used to verify that the custom event emits the message with the expected value.

    Conclusion

    Component testing is a type of software testing that focuses on testing individual components or modules of a software application in isolation. component testing examines each functionality working as expected or not.

    Angular provides various tools for testing like testBed which is used to create the testing environment, and RouterTestingModule is used to set up a testing environment for Angular's routing infrastructure. It tests that components navigate to the correct routes based on certain actions or conditions.

    By mastering the above-mentioned functional testing techniques, you can ensure the quality, reliability, and maintainability of your Angular applications through comprehensive large component testing.

    24

    Related articles

    This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.