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
相关产品推荐
相关产品推荐

