0

How could I test this code:

this.window.addEventListener('pathSelectedFromMfe', (customEvent: CustomEvent) => {
  this.window.location.href = customEvent.detail.path;
});

This is what I have tried:

Let's say that you want to test the window.addEventListener() method without modifying the window.location.href property when an event is received. Usually an easy way to do this would be by following this answer: How to test angular event listeners?. As you can see, no mock is required in that case.

If you proceed with the previous solution for my code, the test will fail with an error like this: Some of your tests did a full page reload!.

Next step for me was to find out how can I test just the this.window.location.href = customEvent.detail.path; line and this can be done by following this answer: Angular unit testing window.location.href. As you can see, a mock is being used in this case.

Now I believe that this solution (by using a mock) could be modified to also test the window.addEventListener() method, but I am not sure how to do that.

iulian16
  • 91
  • 1
  • 4

1 Answers1

0

So I have found a solution, but I am not sure whether it is the optimal one. Basically what I did was to mock the window.addEventListener() method but this implied that I couldn't use the window.dispatchEvent() method anymore in my test (maybe that one can be mocked as well, but I don't see how they can work together in that case). These are the files that I had to create/modify to test my code:

window-token.ts

import { InjectionToken } from '@angular/core';

export const WindowToken = new InjectionToken('Window');

export function windowProvider() {
  return window;
}

app.module.ts

add this line to the providers array:

{ provide: WindowToken, useFactory: windowProvider }

my.component.ts

import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { WindowToken } from './core/window-token/window-token';

@Component({
  selector: 'app-my',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class MyComponent {

  constructor(@Inject(WindowToken) private window) {
    this.window.addEventListener('pathSelectedFromMfe', (customEvent: CustomEvent) => {
      this.window.location.href = customEvent.detail.path;
    });
  }
}

my.component.spec.ts

import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { WindowToken } from './core/window-token/window-token';
import { MyComponent } from './my.component';

describe('MyComponent', () => {
  let fixture: ComponentFixture<MyComponent>;

  let customPath = '';
  const windowMock = {
    location: {
      _href: '',
      set href(url: string) {
        this._href = url;
      },
      get href() {
        return this._href;
      }
    },
    addEventListener(event: string, callback: (customEvent: CustomEvent) => void) {
      callback(new CustomEvent(event, { detail: { path: customPath } }));
    }
  };

  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [MyComponent],
        providers: [
          { provide: WindowToken, useValue: windowMock }
        ]
      }).compileComponents();
    })
  );

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    fixture.detectChanges();
  });

  it('should handle pathSelectedFromMfe window event', () => {
    const setHrefSpy = spyOnProperty(windowMock.location, 'href', 'set');

    customPath = '/search';
    // the constructor is called when we create the MyComponent, so we invoke the creation again to use the new value for customPath
    TestBed.createComponent(MyComponent);
    expect(setHrefSpy).toHaveBeenCalledWith('/search');
  });
});

I don't think this is the best solution because the constructor code is called straight away inside the beforeEach() method from the unit test when we call TestBed.createComponent(MyComponent);. This means that we call this.window.location.href = '' before the unit test even starts.

Every time we want to test the code with a different value for the event that is received we need to call TestBed.createComponent(MyComponent);, whereas when the real window object is used (like in the solution linked in my initial question message) we only need to dispatch a new event. Also in this solution the event has to be modified through an auxiliary variable, in my case the customPath string.

For now this solution works for me, but if anyone finds a better one, please add a response.

PS: the code is part of an Angular 13 project.

iulian16
  • 91
  • 1
  • 4