Angular How to test window.location.href with jasmine, karma
Asked Answered
I

2

7

In Angular project (Angular CLI: 9.1.4) I have a component with has function using window.location.href = EXTERNAL_URL

a.component.ts

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

@Component({
  selector: 'a',
  templateUrl: './a.component.html',
  styleUrls: ['./a.component.scss']
})
export class AComponent implements OnInit {
  constructor(private service: Service) {}

  ngOnInit(): void {}

  redirect() {
    window.location.href = 'https://sample.com'
  }
}

For unit test, I have a.component.spec.ts

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

import { AComponent } from './A.component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
      declarations: [AComponent]
    }).compileComponents();
  }));

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

  });

  describe('test', () => {
    it('test redirect function', () => {
      component.redirect();

      expect(window.location.href).toEqual('https://sample.com');
    });
  });
});

it must check the URL 'window.location.href' is same as I expected.

But when running test, showed error which says

Expected 'http://localhost:0987/context.html' to equal 'https://sample.com'

Why window.location.href param is like this?

Also, Karma (v5.0.4) showed test result on browser normally, but on this test, browser page is suddenly changed.

enter image description here

It happens because 'window.location.href' had run on test browser( it tried to open https://sample.com). But because of this redirection, I cannot see Karma result.

So, my question is

  • How can I test window.location.href
  • How can I stop redirection on karma test browser

P.S. I had thought that using window.location.href was not good, but I couldn't find any ways to redirect external URL.

Iover answered 15/7, 2021 at 8:12 Comment(2)
Does this answer your question? Angular 7 unit test with window.location.hrefCheshire
@Cheshire it says need to test everything except the actual setting of window.location.href - . Maybe I don't need to test window.location.href itself. I'll remove this test( still, I wonder if I could solve the second question; stop redirection ...). Thank youIover
W
2

Here the biggest problem is in the window object itself. But we can redefine it for the test environment. My solution:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'a',
      templateUrl: './a.component.html',
      styleUrls: ['./a.component.scss']
    })
    export class AComponent {
      private window = window;

      constructor() {}

      redirect() {
        this.window.location.href = 'https://sample.com'
      }
    }

spec:

    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { AComponent } from './A.component';
    import { By } from '@angular/platform-browser';
    import { HttpClientModule } from '@angular/common/http';
    
    describe('#AComponent', () => {
      let component: AComponent;
      let fixture: ComponentFixture<AComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientModule],
          declarations: [AComponent]
        }).compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(AComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      describe('test', () => {
        it('test redirect function', () => {
          let currentLocation;
          component['window'] = {
            location: {
             set href(value) { currentLocation = value;}
            }
          } as Window & typeof globalThis;

          component.redirect();
    
          expect(currentLocation).toBe('https://sample.com');
        });
      });
    });
Worn answered 6/6, 2022 at 10:57 Comment(0)
Z
1

The cleanest solution is to use an InjectionToken to provide the window-object.

See this blog post for more details on this approach for the window object.

Naive approach

Roughly it can achieved as:

// variable in component class
private window: (Window & typeof globalThis) | null

// component constructor 
constructor(
  @Inject(DOCUMENT) private document: Document,
) {
  this.window = this.document.defaultView
}

// redirection in component function
this.window?.location.assign('https://sample.com')

// in unit test
it('test redirect function', () => {
  let currentLocation;
  component['window'] = {
    location: {
      set href(value) { currentLocation = value;}
    }
  } as Window & typeof globalThis;

  component.redirect();
    
  expect(currentLocation).toBe('https://sample.com');
}

Note that the test is quite similar to the other answer. Though by using an InjectionToken it has the major benefit that it does not depend on the browser being the running environment.

You could also directly use this.document.defaultView instead of assigning this.window though. It depends on your taste.

Cleaner approach

It's better to define external navigation as a service and provide the window as a token for window because this allows the most efficient stubbing.

// The navigation service
export class ExternalNavigationService {
  private API_HOST = 'example.com'
  private DASHBOARD_URL = `${this.API_HOST}/dashboard`

  constructor(
    @Inject(WINDOW) public window: Window & typeof globalThis,
  ) { }

  toDashboard(){
    this.navigate(this.DASHBOARD_URL)
  }

  private navigate(url: string | URL){
    this.window.location.assign(url)
  }
}
import { InjectionToken } from '@angular/core';

export const WINDOW = new InjectionToken<Window>('Global window object', {
  factory: () => window
});
// Unit tests for the navigation sevice 
describe('ExternalNavigationService', () => {
  let externalNavigationService: ExternalNavigationService
  let windowStub: Window & typeof globalThis

  beforeEach(() => {
    windowStub = {
      location: { assign(value: string) { return value }},
    } as unknown as Window & typeof globalThis

    TestBed.configureTestingModule({
      providers: [ { provide: WINDOW, useValue: windowStub } ]
    })
    externalNavigationService = TestBed.inject(ExternalNavigationService)
  })

  describe('#navigate', () => {
    it('redirects to the given url', () => {
      spyOn(windowStub.location, 'assign')
      externalNavigationService['navigate']('/test')
      expect(windowStub.location.assign).toHaveBeenCalledWith('/test')
    })
  })

  describe('#toDashboard', () => {
    it('navigates to the dashboard', () => {
      spyOn(externalNavigationService, <never>'navigate')
      externalNavigationService.toDashboard()
      expect(externalNavigationService['navigate']).toHaveBeenCalledWith('example.com/dashboard')
    })
  })
})

Now you can mock the ExternalNavigationService anywhere and just test that itself calls the required navigation method:

expect(mockedExternalNavigationService.toDashboard).toHaveBeenCalled()
Zimbabwe answered 20/2 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.