DEV Community

Achilles Moraites
Achilles Moraites

Posted on

Advanced Angular Testing using Jasmine

When testing our apps there are times when we need to control things that are beyond our control, like the window object.
One common scenario is when we need to test our code against browser specific APIs.

If you are looking how to upgrade your testing skills this is the article you have been looking for!

Show me the code

// history.component.ts
import { Component, OnInit } from '@angular/core';

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

  constructor() { }

  ngOnInit() {
  }

  goBack() {
    if (window.history.length === 1) {
      console.log('length 1');
    } else {
      window.history.back();
    }
  }

  saveFile() {
    const blob = new Blob([''], {
      type: 'text/html'
    });
    // IE
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveOrOpenBlob(blob, 'file.txt');
    } else {
      console.log('custom handling');
    }
  }

}
Enter fullscreen mode Exit fullscreen mode

Now lets test the goBack() method

As you know already the window.history is read only.
We need to test two cases:

  • history.length == 1
  • history.length > 1

in our history.component.spec.ts
we use the spyOnProperty to mock the window.history.length to be able to test our both cases:

  it('should execute "goBack" as expected when history === 1', () => {
    // spy on console.log()
    spyOn(console, 'log');
    // here we mock the history length to be 1
    spyOnProperty(window.history, 'length', 'get').and.returnValue(1);
    component.goBack();

    expect(console.log).toHaveBeenCalledWith('length 1');
  });

  it('should execute "goBack" as expected when history > 1', () => {
    // spy on window.history.back()
    spyOn(window.history, 'back');
    // here we mock the history length to be 2
    spyOnProperty(window.history, 'length', 'get').and.returnValue(2);
    component.goBack();

    expect(window.history.back).toHaveBeenCalled();
  });
Enter fullscreen mode Exit fullscreen mode

that was easy :)

Now lets tackle a more interesting case, what about testing a browser specific api?

Testing browser specific APIs

Now in the saveFile() method we are using a browser specific API, here things are getting more interesting.

The window.navigator.msSaveOrOpenBlob is available only on IE,
on other supported browsers we have a different implementation.

Let's dive to our test code!

 it('should execute "saveFile" as expected on IE', () => {
    // create a mock navigator
    const mockNavigator = jasmine.createSpyObj(['msSaveOrOpenBlob']);
    // here we use the mockNavigator to simulate IE
    spyOnProperty(window, 'navigator', 'get').and.returnValue(mockNavigator);
    component.saveFile();

    // verify that method has been called :)
    expect(mockNavigator.msSaveOrOpenBlob).toHaveBeenCalled();
  });

  it('should execute "saveFile" as expected on browsers other than IE', () => {
    // spy on console.log()
    spyOn(console, 'log');
    // create a mock navigator
    const mockNavigator = jasmine.createSpyObj(['']);
    // here we use the mockNavigator to simulate behavior
    spyOnProperty(window, 'navigator', 'get').and.returnValue(mockNavigator);
    component.saveFile();

    // verify that method has been called :)
    expect(console.log).toHaveBeenCalledWith('custom handling');
  });

Enter fullscreen mode Exit fullscreen mode

Here we mocked the window.navigator to be able to simulate the behavior on both cases!

Summary

Today we learned about mocking the window object to be able to do tests against browser specific APIs.
By using this technique you will be able to mock anything you need to test your code.

I hope you enjoyed it,
Happy coding :)

Top comments (0)