Angular Jest单元测试报错:TypeError: 无法读取undefined的subscribe属性
Angular Jest测试报错:TypeError: Cannot read properties of undefined (reading 'subscribe')
我是Angular Jest测试新手,在编写EhiExportPendingComponent的单元测试时遇到报错:TypeError: Cannot read properties of undefined (reading 'subscribe')。该组件使用@ngrx/store的Store类、EhiExportCommunicationService等服务,在ngOnInit及cancelRequest方法中存在多处subscribe逻辑。
组件代码
@Component({ selector: 'tw-ehi-export-pending', templateUrl: './ehi-export-pending.component.html' }) export class EhiExportPendingComponent implements OnInit, OnDestroy { subs = new SubSink(); gridData: any[] = []; orgName = ''; constructor( private ehiexportstore: Store < ehiexportreducer.EHIExportState > , private ehiExportCommService: EhiExportCommunicationService, private hostComm: HostCommunicationService, private ehiExportService: EhiExportService, private cdr: ChangeDetectorRef ) {} ngOnInit(): void { this.orgName = this.hostComm.getUser().organization; this.subs.sink = this.ehiexportstore.pipe(select(ehiexportreducer.getPendingRequests)).subscribe((data) => { this.gridData = data ? [...data] : []; this.cdr.detectChanges(); }); this.subs.sink = this.ehiExportCommService.selectedSideMenuOption$.subscribe((selectedMenu: string) => { if (selectedMenu.toLocaleLowerCase() === 'pending') { this.ehiexportstore.dispatch(EHIExportActions.LoadPendingRequests()); } }); this.subs.sink = this.ehiExportCommService.pendingDataRefresh$.subscribe(() => { this.ehiexportstore.dispatch(EHIExportActions.LoadPendingRequests()); }); this.ehiexportstore.dispatch(EHIExportActions.LoadPendingRequests()); } cancelRequest(requestId: number): void { this.hostComm.cancelExportDialog().then((choice) => { if (choice === 'continue') { this.ehiExportService.cancelRequest(requestId).subscribe({ next: (): void => this.ehiExportCommService.triggerPendingDataRefresh(), error: (): void => { console.log(`Something went wrong while canceling the request with request ID ${requestId}.`); this.ehiExportCommService.triggerPendingDataRefresh(); } }); } }); } ngOnDestroy(): void { this.subs.unsubscribe(); } }
测试代码
describe('EhiExportPendingComponent', () => { let component: EhiExportPendingComponent; let fixture: ComponentFixture < EhiExportPendingComponent > ; let mockStore: MockStore < ehiexportreducer.EHIExportState > ; beforeEach(async () => { await TestBed.configureTestingModule({ providers: [ provideMockStore(), { provide: EhiExportCommunicationService, useValue: new MockEhiExportCommunicationService() }, { provide: HostCommunicationService, useValue: new MockHostCommunicationService() }, { provide: EhiExportService, useValue: EhiExportService } ], declarations: [EhiExportPendingComponent] }).compileComponents(); fixture = TestBed.createComponent(EhiExportPendingComponent); mockStore = TestBed.get(Store); component = fixture.componentInstance; mockStore.overrideSelector(ehiexportreducer.getPendingRequests, pendingRequests); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
问题原因分析
- EhiExportService的Mock错误:测试代码中直接将
EhiExportService类作为useValue传入,组件注入的是类本身而非实例,调用cancelRequest时会得到undefined,触发subscribe报错。 - MockEhiExportCommunicationService可能未正确实现Observable属性:如果这个mock类没有将
selectedSideMenuOption$和pendingDataRefresh$定义为可订阅的Observable对象,组件在ngOnInit中订阅时也会报错。 - MockHostCommunicationService的getUser方法可能未返回有效对象:虽然当前报错是
subscribe相关,但如果getUser()返回undefined,也会导致organization读取失败。
修复方案
1. 正确Mock EhiExportService
将EhiExportService的mock改为返回包含cancelRequest方法的对象,该方法返回一个可订阅的Observable:
{ provide: EhiExportService, useValue: { cancelRequest: jest.fn(() => of({})) // of()来自rxjs,返回空的成功Observable } }
2. 完善MockEhiExportCommunicationService
确保mock类中的Observable属性正确初始化,同时实现triggerPendingDataRefresh方法:
class MockEhiExportCommunicationService { // 用BehaviorSubject确保有初始值,避免订阅时无数据 selectedSideMenuOption$ = new BehaviorSubject<string>('pending'); pendingDataRefresh$ = new BehaviorSubject<void>(undefined); triggerPendingDataRefresh = jest.fn(() => this.pendingDataRefresh$.next()); }
3. 完善MockHostCommunicationService
确保getUser()返回包含organization的对象,同时实现cancelExportDialog方法:
class MockHostCommunicationService { getUser = jest.fn(() => ({ organization: '测试组织' })); cancelExportDialog = jest.fn(() => Promise.resolve('continue')); // 默认返回continue,方便测试cancelRequest逻辑 }
4. 优化测试代码中的Store获取方式
使用Angular推荐的TestBed.inject替代TestBed.get:
mockStore = TestBed.inject(Store) as MockStore<ehiexportreducer.EHIExportState>;
修复后的完整测试代码
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { EhiExportPendingComponent } from './ehi-export-pending.component'; import { EhiExportCommunicationService } from '../services/ehi-export-communication.service'; import { HostCommunicationService } from '../services/host-communication.service'; import { EhiExportService } from '../services/ehi-export.service'; import { ehiexportreducer } from '../store/reducers/ehi-export.reducer'; import { BehaviorSubject, of } from 'rxjs'; class MockEhiExportCommunicationService { selectedSideMenuOption$ = new BehaviorSubject<string>('pending'); pendingDataRefresh$ = new BehaviorSubject<void>(undefined); triggerPendingDataRefresh = jest.fn(() => this.pendingDataRefresh$.next()); } class MockHostCommunicationService { getUser = jest.fn(() => ({ organization: '测试组织' })); cancelExportDialog = jest.fn(() => Promise.resolve('continue')); } describe('EhiExportPendingComponent', () => { let component: EhiExportPendingComponent; let fixture: ComponentFixture<EhiExportPendingComponent>; let mockStore: MockStore<ehiexportreducer.EHIExportState>; let mockEhiExportService: typeof EhiExportService; // 模拟pending请求数据 const pendingRequests = [{ id: 1, name: '测试请求' }]; beforeEach(async () => { await TestBed.configureTestingModule({ providers: [ provideMockStore(), { provide: EhiExportCommunicationService, useValue: new MockEhiExportCommunicationService() }, { provide: HostCommunicationService, useValue: new MockHostCommunicationService() }, { provide: EhiExportService, useValue: { cancelRequest: jest.fn(() => of({})) } } ], declarations: [EhiExportPendingComponent] }).compileComponents(); fixture = TestBed.createComponent(EhiExportPendingComponent); mockStore = TestBed.inject(Store) as MockStore<ehiexportreducer.EHIExportState>; mockEhiExportService = TestBed.inject(EhiExportService); component = fixture.componentInstance; mockStore.overrideSelector(ehiexportreducer.getPendingRequests, pendingRequests); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
内容的提问来源于stack exchange,提问作者winston dsouza
相关产品推荐
相关产品推荐

