Angular 2 Jasmine unit testing (quick overview)

KEYWORDS:

  • describe: optional, describe the entire unit testing class
  • it: must include it when writing isolated unit test
  • async: tell the test framework to wait until the return promise or observable is completed before treating the test as completed. when all async operations are completed the next test (if exists) is able to start executing
  • beforeEach: for creating new instances for another test to start from the beginning
  • TestBed: creates components instance, configure module, simulate module
  • Access field/variable: fixture.componentInstance.some_variable_name OR if already instantiated then component.some_variable_name
  • detectChanges: only triggered when an property change occurs inside a .ts file
  • configureTestingModule: configures a “fake” module
  • toBe vs toEqual: toBe object(general) equality vs deep equality
  • fixture.whenStable().then(() => {}); executes after all async operations are executed/finished
  • tick() simulates the passage of time until all async operations above are finished. Must go with fakeAsync keyword, otherwise error will appear
  • fixture.whenStable() vs tick();
    They both do the same. tick() is more prudent to use because you have to give it (in the header of the test) a fakeAsync which means that everything is called synchronously inside the test. The code after the tick() method indicates that all code above has executed (async operations above) and that you can now proceed with testing the result of these above async operations.
    Fixture.whenStable() however can give a false positive if async() is omitted in the header of the test. Therefore the test will complete before fixture.whenStable executions.
  • A Spy is a feature of Jasmine which lets you take an existing class, function, object and mock it in such a way that you can control what gets returned from functions.


Configuring Test Module

...

innerHTML

    let fixture= TestBed.createComponent(AppComponent);
    let element1= fixture.debugElement.query(By.css(".heading")).nativeElement.textContent; //must include the point prefix (.)
    expect(element1).toContain("come")
});

 OR

  it ("test input innerHTML example", async()=>{
     let fixture= TestBed.createComponent(AppComponent);
     let element2= fixture.debugElement.nativeElement.querySelector(".heading");
     expect(element2.textContent).toContain("come");
});

input HTML tag

    it ("test input input-value attribute example", async()=>{
     let fixture= TestBed.createComponent(AppComponent);
     let element1= fixture.debugElement.nativeElement.querySelector("input");
     expect(element1.getAttribute("value")).toContain("som");
});

CSS style

    it ("test input input-value attribute example", async()=>{
     let fixture= TestBed.createComponent(AppComponent);
     let element1= fixture.debugElement.nativeElement.querySelector("input");
     expect(element1.getAttribute("value")).toContain("som");
});
    it ("test css style", async()=>{
     let fixture= TestBed.createComponent(AppComponent);
     let element1= fixture.debugElement.nativeElement.querySelector("h1");
     expect(element1.style.backgroundColor).toBe("blue"); // style attribute is the key for testing css
});

Class property testing


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

beforeEach(() => {
       TestBed.configureTestingModule({
           declarations: [AppComponent]
       });
     });

it('check title class property', async()=> {
  let fixture= TestBed.createComponent(AppComponent);
   expect(fixture.componentInstance.title).toBeDefined()
 });


Service testing

import { TestBed, async, inject } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component'
import { MyServiceService } from './my-service.service'

beforeEach(() => {
let userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User'}
};

//simulate module
TestBed.configureTestingModule({
declarations: [ AppComponent],
providers: [ {provide: MyServiceService, useValue: userServiceStub } ]
});
});

it('should test service object property value', inject([MyServiceService], (userService) => {
let fixture=TestBed.createComponent(AppComponent);
fixture.detectChanges();
expect(userService.user.name).toContain('se');

}));

Pipe testing

import { TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component'
import { TryPipe} from './try.pipe'

it ("should check pipe content", async()=>{
    let piping= new TryPipe();  //create an instance of the pipe
    expect(piping.transform("")).toEqual("something"); //transform returns something initially
});

Directive testing

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { By } from '@angular/platform-browser';
import { FirstOneDirective } from './first-one.directive'
import { NO_ERRORS_SCHEMA }  from '@angular/core';

beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [ AppComponent, FirstOneDirective ],
    schemas:      [ NO_ERRORS_SCHEMA ]
  }).createComponent(AppComponent);

});
it('should have skyblue css background, set from the directive', async()=> {
 //checking the code from the directive implementation
  const fixture=TestBed.createComponent(AppComponent);
  const de = fixture.debugElement.query(By.css('.nesto'));
  const bgColor = de.nativeElement.style.backgroundColor;
  console.log(de.nativeElement.textContent);
  expect(bgColor).toBe('cyan');
 });

([ngModel]) testing

  it('should pass twice, passes but because of two way data binding ngModel', async(() => {
    const fixture1 = TestBed.createComponent(ChangeRestApiComponent);
     fixture1.detectChanges();
     fixture1.whenStable().then(() => {
       let input_val = fixture1.debugElement.query(By.css('#first')).nativeElement;
       expect(input_val.getAttribute('value')).toBeNull();
       input_val.value = 'localhost';

       fixture1.detectChanges();
       input_val.dispatchEvent(new Event('input'));

       expect(fixture1.componentInstance.tempUrl).toContain('localhost');
     });
  }));

Check something to be false (not to contain a substring)

  it('should not contain full url paths, protocol e.g. http://', () => {
    component.array.forEach((item) => {
        expect(item.url.indexOf('http') === 1).toBe(false);
    });
  });

Checking html CHECKBOX element “checked” attribute

  it('check if checkbox checked attribute changed from default false to true', async() => {
    const input = fixture.debugElement.query(By.css('.className')).nativeElement;
    expect(component.testing).toBe(false);
    component.testing = true;
    fixture.detectChanges();
    expect(input.checked).toBe(true);
  });

Check value after BUTTON onClick event executes

it('check if on-button (click) event changes the value in the HTML', async() => {
     const div = fixture.debugElement.query(By.css('.clicking')).nativeElement;
     expect(component.clickMe).toBeUndefined();
     component.clickThisButton();
     fixture.detectChanges();
     expect(div.innerText).toEqual(component.clickMe);

  });

Check new INPUT value after change event executes

  it('change input value and check if change occured', async() => {
   const input = fixture.debugElement.query(By.css('.inputlast')).nativeElement;
   expect(input.value).toBe('undefined');
   component.changeMe = 'changed';
   fixture.detectChanges();
   expect(input.value).toEqual('changed');
  });

EventHandler(‘click’) testing

...

Routing testing

...

Local storage testing

...