Angular2 – Unit testing – introduction

Introduction

This Section only provides an Introduction to Unit Testing an Angular 2 App.

It’s NOT a detailed Guide about that neither is it a detailed Introduction to Unit Testing in general. Check out the “Further Resources” (last Lecture in this Module) to dive deeper into these topics.

Why units tests?

Captura de pantalla 2017-06-02 a las 11.48.17

Captura de pantalla 2017-06-02 a las 11.48.34

Analyzing the testing setup (as created by the CLI)

  • In order to simulate we need to run the app in the testing environment as it would be in the browser.
  • Our test runner will mount the testing environment and will read spec files to execute the tests.
  • Lets see one example
  • app/app.component.spec.ts
import { TestBed, async } from '@angular/core/testing';

import { AppComponent } from './app.component';

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

  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

  it(`should have as title 'app'`, async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app');
  }));

  it('should render title in a h1 tag', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!');
  }));
});
  • In the beforeEach we configure the module for our testing with the TestBed utility.
  • toBeTruthy check there is an existing app
  • In the third test we check one value in the template. For that we need to

Running Tests (with the CLI)

ng test

Adding a Component and some fitting tests

  • Lets create a new user component: ng g c user
  • Lets create a basic test for this component: check if instance is existing

app/user/user.component.ts

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

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {

  user: {name: string};
  isLoggedIn = false;

  constructor() { }

  ngOnInit() {
  }

}

app/user/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 be created', () => {
    expect(component).toBeTruthy();
  });
});

app/user/user.component.html

<div *ngIf="isLoggedIn">
  <h1>User logged in</h1>
  <p>User is {{ user.name }}</p>
</div>
<div *ngIf="!isLoggedIn">
  <h1>User not logged in</h1>
  <p>Please log in first</p>
</div>

 

Testing Dependencies: Components and Services

  • Lets add a service component

user.service.ts

export class UserService {
  user = {
    name: 'Max'
  };
}

user.component.spec.ts

  it('should use the user name from the service', ()=> {
    let userService = fixture.debugElement.injector.get(UserService);
    fixture.detectChanges();
    expect(userService.user.name).toEqual(component.user.name);
  });
  it('should display the user name if user is logged in', ()=> {
    component.isLoggedIn = true;
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('p').textContent).toContain(component.user.name);
  });
  it('should not display the user name if user is not logged in', ()=> {
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('p').textContent).not.toContain(component.user.name);
  });

 

Simulating Asyncs Tasks

  • We create a data service to simulate a remote service
  • In the test we wrap tests with async method and we use fixture.whenStable to check the assertions.

/shared/data.service.ts

export class DataService {
  getDetails() {
    const resultPromise = new Promise((resolve,reject)=>{
      setTimeout(()=> {
        resolve('Data');
      },1500);
    });
    return resultPromise;
  }
}

/user/user.component.ts

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

import {UserService} from './user.service';
import {DataService} from '../shared/data.service';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  providers: [UserService, DataService]
})
export class UserComponent implements OnInit {

  user: {name: string};
  isLoggedIn = false;
  data:string;

  constructor(private userService: UserService, private dataService: DataService) { }

  ngOnInit() {
    this.user = this.userService.user;
    this.dataService.getDetails().then((data:string)=> this.data = data);
  }

}

/user/user.component.spec.ts

  it('shouldnt fetch data successfully if not called asynchronously', ()=> {
    let dataService = fixture.debugElement.injector.get(DataService);
    let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
    fixture.detectChanges();
    expect(component.data).toBe(undefined);
  });

  it('should fetch data successfully if called asynchronously', async(()=> {
    let dataService = fixture.debugElement.injector.get(DataService);
    let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
    fixture.detectChanges();
    fixture.whenStable().then(()=> {
      expect(component.data).toBe('Data');
    });
  }));

 

Using “fakeAsync” and “tick”

  • It is another way for testing and async feature:
  it('should fetch data successfully if called asynchronously', async(()=> {
    let dataService = fixture.debugElement.injector.get(DataService);
    let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
    fixture.detectChanges();
    fixture.whenStable().then(()=> {
      expect(component.data).toBe('Data');
    });
  }));

 

Isolated vs Non-isolated tests

  • There can be some services or pipes with functions not related to Angular. We just make simple tests for those
  • For example a reverse pipe and its test

/shared/reverse.pipe.ts

import { Pipe } from '@angular/core';
@Pipe({
  name: 'reverse'
})
export class ReversePipe {
  transform(value: string) {
    return value.split("").reverse().join("");
  }
}

/shared/reverse.pipe.spec.ts

import { ReversePipe } from './reverse.pipe';

describe('ReversePipe', () => {

  it('should be created', () => {
    let reversePipe = new ReversePipe();
    expect(reversePipe.transform('hello')).toEqual('olleh');
  });
});

 

Further resources & Where to go next

This Module only provides a brief and basic Introduction to Angular 2 Unit Tests and the Angular 2 Testing Suite. This Course isn’t focused on Testing.

If you want to dive deeper, the official Docs actually are a great place to start. There you’ll also find a Non-CLI Setup!

Official Docshttps://angular.io/docs/ts/latest/guide/testing.html

I can also recommend the following Article: https://semaphoreci.com/community/tutorials/testing-components-in-angular-2-with-jasmine

For more Information on how to run Tests with the CLI have a look at their official Docs:

=> Unit Tests: https://github.com/angular/angular-cli#running-unit-tests

=> E2E Tests: https://github.com/angular/angular-cli#running-end-to-end-tests