2

Basic Introduction to Unit Testing in Angular

 2 years ago
source link: https://blog.knoldus.com/basic-introduction-to-unit-testing-in-angular/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Reading Time: 6 minutes

What is Unit Testing?

Unit testing is testing a unit in an isolated environment. A unit can be a class, component, service, directive module, etc. which can be logically separated from the software. Any unit in an app is not isolated, it’s quite normal that it will be depending on the other units in an application for resources like data or methods.

So if we do an integrated test of the application and it fails then it’s hard to identify where exactly the code is breaking. So the purpose of unit testing is to test each unit individually and see if it’s working fine. 

Benefits of Unit Testing

Reveal design mistakes

You may encounter difficulty while writing tests which might reveal that the design is not correct and you may be violating some important coding principles. Or after running the test it shows unexpected behavior. 

Add new features without breaking anything

If you add any new feature into existing code and after running test passes then you can be confident it won’t break the application.

Simplifies debugging process

As discussed earlier it makes it easy to exactly identify where the code is breaking.

Tests make developers more confident about their work

So, we will understand unit testing in angular by looking at some basic simple examples and then getting to know why and how we have done.

Getting started by creating a new project first

First, we will need to have an application to test, so creating a new project using angular CLI will install everything we need to have to test the application. 

Create a new project :

 new angular-unit-testing

so we just created a new angular project named ‘angular-unit-testing’

Everything we are going to need is installed.

see the package.json

    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.3.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.0",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",

let’s understand what we have installed

Jasmine-core: Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM.

Karma: Karma is essentially a tool that spawns a web server that executes source code against test code for each of the browsers connected. The results of each test against each browser are examined and displayed via the command line to the developer such that they can see which browsers and tests passed or failed.

Example: 1

DOM testing

So we begin with our first very simple setup to test. In this project, we have a title property with value “angular unit testing”  in app.component.ts file and this is rendered in an h1 tag in app.component.html. So the aim of this test will be to check is the title has the desired value and whether it is rendered. 

app.component.html

<h1>{{title}}</h1>

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'angular unit testing';

  constructor() {
  }

  ngOnInit() {
  }
}

app.component.spec.ts

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

describe('AppComponent', () => {
  let app: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  beforeEach(async () => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    app = fixture.componentInstance;
  });

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

  it(`should have as title 'angular-unit-testing'`, () => {
    expect(app.title).toEqual('angular unit testing');
  });

  it('should render title', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('angular unit testing');
  });
});

So let’s look into what all has been going in this code.

First, we imported all the imports required.

describe(): Covers all the tests of a component, it’s like the marks a suite of all the different test cases we are going to test for a component.

BeforeEach(): Before running each test there are few preconditions to set the environment for the test to run, this is common for all the tests of a unit. beforeEach() id used set those conditions and is called before each test runs. preconditions can be any other component, module, service, etc the component being test requires. (We use an async before each. The purpose of the async is to let all the possible asynchronous code to finish before continuing.

Testbed: creates a dynamically-constructed Angular test module that emulates an Angular @NgModule. The TestBed.configureTestingModule() method takes a metadata object that can have most of the properties of an @NgModule.

A spec file does not know any of the custom components, classes, imports you have mentioned in the ts file or module, everything needs to be mentioned and imported in the spec file for creating the environment to run the tests. Then we call the createComponent() method after we configure the TestBed.

TestBed.createComponent(): Creates an instance of the AppComponent, adds a corresponding element to the test-runner DOM, and returns a ComponentFixture.

CompnentFixture: This is a test harness for interacting with the created component and its corresponding element.

  1. This is the first test of this test suit we test that confirms that the component is created and exists.
  2. In the second test, we test whether or not the value in the h1 tag is what we have given in the title variable.

detectChange() : In the actual production, data binding is done automatically but in TestBed.createComponent() does not do data binding unless we trigger by detectChange().

nativeElement() : nativeElement() will be HTML element, and we can access its child and dive into its DOM tree using querySelectors().

Example: 2

Testing Component Class

In this example, we will look at how we test a class of a component. For this we have a simple program that gives a message based on the basis of whether the user has logged in or not and the user can change the login status using the logIn() method.

user.component.ts

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

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.scss']
})
export class UserComponent {
  isLoggedIn = false;
  message: string;

  logIn() {
    this.isLoggedIn = true;
  }

  giveMessage() {
    if (this.isLoggedIn) {
      this.message = 'welcome';
    } else {
      this.message = 'please log in';
    }
  }
}

user.component.spec.ts

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

import { UserComponent } from './user.component';

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

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

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

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

  it('should give message \' please log in \' ', () => {
    component.giveMessage();
    expect(component.message).toEqual('please log in');
  });

  it('should change isLoggedIn to true', () => {
    component.logIn();
    expect(component.isLoggedIn).toEqual(true);
  });

  it('should give message to welcome on logIn', () => {
    component.logIn();
    component.giveMessage();
    expect(component.message).toEqual('welcome');
  });
});

The pretty basic program now let’s go through how we tested it.

A very crucial part of testing, in general, is that we have to think of different all the possible scenarios to test the app completely. In this example, we have to test whether we are getting the right message according to the logged-in status and is logIn() method when called changing the status.

  • Scenarios will be whether first, the giveMessage() should give ‘please log in’.
  • If logIn() is called it should change the status to true.
  • After logIn() changes the status the message also changed accordingly..

So we have an understanding of what we should be expecting. The initial setup before each test is pretty much the same as discussed in the previous example.

  1. In the first test, we call the component’s giveMessage() method to check that initially, the message is ‘please log in’
  2. For the second we call the component’s logIn() method to see if it changed the status for loggedIn and expect it to be true.
  3. Finally, in the last test, we are testing that after the logIn() changes the loggedIn status the message is also will be changed to ‘welcome’

Third Example

Testing Component With Dependency

Components generally have dependencies. Till now we have seen examples that were very basic to get comfortable with concepts but now we will look at an example that will truly justify the purpose of unit testing.

This studentComponent gives us the detail for student result and the student data will come from service, StudentService.

student.service.ts

export class StudentService {
  studentData = {name: 'max', result: 'pass'};
}

student.component.ts

import { Component, OnInit } from '@angular/core';
import {StudentService} from './student.service';

@Component({
  selector: 'app-student',
  templateUrl: './student.component.html',
  styleUrls: ['./student.component.scss']
})
export class StudentComponent implements OnInit {
  studentDetails: {name: string, result: string};
  constructor(private service: StudentService) { }

  ngOnInit(): void {
    this.studentDetails = this.service.studentData;
  }
}

So after getting the data, any logic could be implemented as per the business requirement like in the previous example. But we are keeping it simple for now.

student.component.spec.ts

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

import { StudentComponent } from './student.component';
import {StudentService} from './student.service';

describe('StudentComponent', () => {
  let component: StudentComponent;
  let fixture: ComponentFixture<StudentComponent>;
  let mockService: Partial<StudentService>;

  beforeEach(() => {
    mockService = {
      studentData : {name: 'test', result: 'pass'},
    };

    TestBed.configureTestingModule({
      declarations: [ StudentComponent ],
      providers: [ { provide: StudentService, useValue: mockService } ],
    })
    .compileComponents();

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

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

  it('student details should equal to the data provided by mock service', () => {
    expect(component.studentDetails).toEqual({name: 'test', result: 'fail'});
  });
});

So now let’s dive into what is happening.

This time along with declaring the component, we have added StudentService as providers but not the real StudentService instead a mock of it.

We don’t need to the real service to test the component, that will not be unit testing. We just need to see if the StudentComponent is working correctly, irrespective of whether or not other units in the app fails. So, we have to make a mock/stub/fake service that gives a mock/data using which we can test. The service could have been an HTTP service, requiring credentials and whatnot, but we are only concern with the StudentComponent.

mockService = {
  studentData : {name: 'test', result: 'pass'},
};
providers: [ { provide: StudentService, useValue: mockService }

We make a mock service and tell angular to use this implementation. And then we test the studentDetails of the component. Expect it to be equal to the mock value we have given.

Conclusion

We went through a few of the concepts and examples to try to explain how to test angular components. I hope this article helps you understand a little bit better on how to use the tools to test angular.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK