Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 286
Ryo Avatar asked May 02 '26 01:05

Ryo


2 Answers

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');
        });
      });
    });
like image 72
Viktor Ivliiev Avatar answered May 04 '26 04:05

Viktor Ivliiev


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()
like image 21
OuttaSpaceTime Avatar answered May 04 '26 03:05

OuttaSpaceTime



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!