Advanced Angular Testing using Jasmine
Tags: angular, javascript, typescript, testing
Published: 2020-03-21 09:18:07


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

```typescript
// 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');
    }
  }

}
```

### 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:
```typescript
  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();
  });
```
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!

```typescript
 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');
  });

```
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 :)