Home > Enterprise >  Mocking the window object in Jest leads to error because of missing properties
Mocking the window object in Jest leads to error because of missing properties

Time:12-05

I am working on an Angular App and need to test a Component with Jest; the app is communicating bi-directional with a Windows Desktop App. I´ve implemented a Service that listens to events from the Desktop app and that all works fine. My problem is the unit test. I know I need to mock and inject the global window object somehow, because in this way, Angular can receive events from Edge and again, that works fine; yet, my unit test is throwing the following error:

: Property 'newWorkItem' does not exist on type 'Window & typeof globalThis'.

     expect(window.newWorkItem).toBeTruthy;

This is my .spec:

/* eslint-disable @typescript-eslint/typedef */
/* eslint-disable jasmine/expect-matcher */
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IwdPluginCompComponent } from './iwd-plugin-comp.component';
import { WebViewService } from '../core/services/web-view.service';
import { AppModule } from '../app.module';



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


  beforeEach(async () => {

    const newWorkItem = jest.fn();
    const myMock1     = jest.fn();
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const a           = new myMock1();
    console.log(myMock1.mock.instances);
    jest.mock('WebViewService');
   
    Object.defineProperty(window, 'newWorkItem', newWorkItem);
    await TestBed.configureTestingModule({
      imports: [RouterTestingModule, AppModule, WebViewService],
      providers: [{ provide: 'Window', useFactory: () => newWorkItem },
    ],
      declarations: [IwdPluginCompComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(IwdPluginCompComponent);
    fixture.detectChanges();
    // eslint-disable-next-line jasmine/prefer-toHaveBeenCalledWith

  });

  it('should have a defined component', () => {

    expect(fixture).toBeTruthy;
    fixture.detectChanges;
    expect(window.newWorkItem).toBeTruthy;
    });
});

This is the component I want to test:

import { ChangeDetectorRef, Component, Inject, Input, OnChanges, OnInit } from '@angular/core';
import { WebViewService } from '../core/services/web-view.service';

export interface IUserData {
  FirstName: String;
  LastName: String;
  InteractionID: String;
}

@Component({
  selector: 'iwd-iwd-plugin-comp',
  templateUrl: './iwd-plugin-comp.component.html',
  styleUrls: ['./iwd-plugin-comp.component.scss'],
  providers: [{ provide: Window, useValue: window }],
})
export class IwdPluginCompComponent implements OnInit, OnChanges {
  public userdata: IUserData = {} as IUserData;
  @Input() public interactionID: unknown = '';

  public constructor(
    public readonly webviewservice: WebViewService,
    @Inject(Window) public window: Window,
    private readonly ref: ChangeDetectorRef
  ) {
    window.newWorkItem = this.newWorkItem;
  }

  public ngOnInit(): void {
    this.ref.detectChanges();
  }

  public ngOnChanges(): void {}

  public newWorkItem(interactionID: String, userData: IUserData): void {
    console.log('NewWorkItem call', interactionID, userData);
    // eslint-disable-next-line @typescript-eslint/typedef
    const event = new CustomEvent('wde.newWorkItem', {
      detail: {
        FirstName: userData.FirstName,
        LastName: userData.LastName,
        InteractionID: interactionID,
      },
      bubbles: true,
      cancelable: true,
      composed: false,
    });
    console.log('NewWorkItem event', event);
    window.dispatchEvent(event);
  }
}

This is the service I am injecting into the component:

/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable @typescript-eslint/typedef */
import { Injectable } from '@angular/core';
import { connectable, fromEvent, map, Observable, shareReplay } from 'rxjs';
import { IUserData } from '../../iwd-plugin-comp/iwd-plugin-comp.component';

@Injectable({
  providedIn: 'root',
})
export class WebViewService {
  private readonly _webViewMessage$ = connectable(
    fromEvent<CustomEvent<IUserData>>(window, 'wde.newWorkItem').pipe(map(ev => ev.detail))
  );
  public webViewMessage$: Observable<IUserData> = this._webViewMessage$.pipe(
    shareReplay({ refCount: true, bufferSize: 1 })
  );

  constructor() {
    this._webViewMessage$.connect();
  }
}

// add chrome to the Window context so typescript stops complaining
declare global {
  interface Window {
    chrome: unknown;
  }
}

This is the global window declaration that I want to test against, which works fine for the application:

import { IUserData } from '../app/iwd-plugin-comp/iwd-plugin-comp.component';

export {};

declare global {
  interface Window {
    newWorkItem(interactionID: String, userdata: IUserData): unknown;
  }
}

As I am really stuck here, I am thankful for any hints or help. I just basically need to get this test running and not test all the details for the component in the first place; this will happen at a later point. Thanks in advance!

CodePudding user response:

I am thinking you need a handle on the Window that is provided and not the one that you have quick access to.

Try this:

it('should have a defined component', () => {

    expect(fixture).toBeTruthy;
    fixture.detectChanges;
    const providedWindow = TestBed.inject('Window');
    expect(providedWindow.newWorkItem).toBeTruthy;
    })

You should get the window that's provided into the component and not the window that's available to be used as we know it.

  • Related