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.