You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest中Mock window对象因属性缺失报错的技术求助

问题:Angular Jest测试中Window类型报错:Property 'newWorkItem' does not exist

错误信息:

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

     expect(window.newWorkItem).toBeTruthy;

测试文件代码

/* 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;
    });
});

待测试组件代码

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);
  }
}

WebViewService代码

/* 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;
  }
}

全局Window声明代码(应用中正常运行)

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

export {};

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

解决步骤

1. 让测试环境识别Window扩展类型

在测试文件顶部引入你的全局Window声明文件,确保TypeScript在测试时能识别window.newWorkItem:

import '../path-to-your-global-window-declaration-file'; // 替换为实际文件路径

如果不想引入全局文件,也可以在测试文件末尾临时添加类型扩展:

declare global {
  interface Window {
    newWorkItem: jest.Mock;
  }
}

2. 修正TestBed配置错误

  • 移除imports数组中的WebViewService:服务不能放在imports里,需通过providers提供mock
  • 修正Window依赖注入:组件注入的是Window类型,不是字符串'Window',要提供包含newWorkItem的mock对象
  • 修正jest.mock路径:指向实际的WebViewService文件路径,而非单纯的服务名

修改后的beforeEach代码:

beforeEach(async () => {
  // 模拟newWorkItem函数
  const mockNewWorkItem = jest.fn();
  // 模拟Window对象
  const mockWindow = {
    newWorkItem: mockNewWorkItem,
    dispatchEvent: jest.fn()
  };

  // 正确mock WebViewService
  jest.mock('../core/services/web-view.service', () => ({
    WebViewService: jest.fn().mockImplementation(() => ({
      webViewMessage$: jest.fn()
    }))
  }));

  await TestBed.configureTestingModule({
    imports: [RouterTestingModule, AppModule],
    declarations: [IwdPluginCompComponent],
    providers: [
      { provide: Window, useValue: mockWindow },
      { provide: WebViewService, useValue: new (await import('../core/services/web-view.service')).WebViewService() }
    ]
  }).compileComponents();

  fixture = TestBed.createComponent(IwdPluginCompComponent);
  fixture.detectChanges();
});

3. 修正测试用例中的语法错误

  • fixture.detectChanges是函数,需添加调用括号fixture.detectChanges()
  • 直接断言注入的mock Window对象,而非全局window(因为组件使用的是注入的Window实例)

修改后的测试用例:

it('should have a defined component', () => {
  expect(fixture.componentInstance).toBeTruthy();
  fixture.detectChanges();
  
  const mockWindow = TestBed.inject(Window);
  expect(mockWindow.newWorkItem).toBeTruthy();
});

内容的提问来源于stack exchange,提问作者DWA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 14:15:38