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

单元测试报错:调用Subject的.pipe方法提示pipe不是函数

单元测试报错:panelService.outputEmitter.pipe is not a function

报错信息

this.panelService.outputEmitter.pipe is not a function

相关代码

TypeScript业务代码

constructor(private panelService: RightPanelService) {        
}

bulkAddCustomCapacitySchedule(roomIds) {
  this.panelService.create(CreateCustomCapacityScheduleComponent, {
    title: 'Add',
    subTitle: 'Custom Capacity Schedules',
    theme: 'success',
    emitterMethod: 'customCapacityScheduleSaveEdit',
    componentParams: { roomIdsArr: roomIds }
  });
  this.panelService.outputEmitter.pipe(takeUntil(this.outputSubscription)).subscribe((event: FilterTagModel) => {
    this.searchRoomConfiguration();
  });
}

RightPanelService代码

import { Injectable } from '@angular/core';
import { Subject, BehaviorSubject } from 'rxjs';
import { PanelOptionsModel } from './panel-options.model';

@Injectable()
export class RightPanelService {
    panel = new Subject();
    loadingPanel = new BehaviorSubject<boolean>(true);
    outputEmitter = new Subject();
    panelContent: any = {
        isShowing: false,
        content: null,
        contentParams: PanelOptionsModel
    };

    constructor() {
    }
    public create(component: any, parameter?: PanelOptionsModel): any {
        this.panelContent.isShowing = true;
        this.panelContent.content = component;
        this.panelContent.contentParams = parameter;
        this.panel.next(this.panelContent);
    }
}        

测试用例代码

import { RoomConfigurationListComponent } from './room-configuration-list.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RightPanelService } from '../../../controls/right-panel/right-panel.service';
import { FormsModule } from '@angular/forms';
import { of } from 'rxjs';

describe('RoomConfigurationListComponent', () => {
  let fixture: ComponentFixture<RoomConfigurationListComponent>;
  let mockPanelService, mockPreRequisiteService;
  let mockTagDropDownData;
  let mockRoomListData;
  let component: RoomConfigurationListComponent;

  beforeEach(async () => {
    mockPanelService = jasmine.createSpyObj(['showLoading', 'create', 'outputEmitter']);
    
    await TestBed.configureTestingModule({
      declarations: [RoomConfigurationListComponent],
      imports: [FormsModule],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      providers: [{
        provide: RightPanelService,
        useValue: mockPanelService
      }]
    }).compileComponents();
    fixture = TestBed.createComponent(RoomConfigurationListComponent);
    component = fixture.componentInstance;
  });

  it('should call onBulkActionClick method', () => {
    spyOn(fixture.componentInstance, 'onBulkActionClick').and.callThrough();
    mockPreRequisiteService.GetDropdownsData.and.returnValue(of(mockTagDropDownData));
    component.selectedRows = mockRoomListData.items;

    fixture.detectChanges();

    fixture.componentInstance.onBulkActionClick();

    expect(fixture.componentInstance.onBulkActionClick).toHaveBeenCalled();
  });
});

错误原因

测试用例中,你用jasmine.createSpyObj创建mockPanelService时,错误地将outputEmitter作为方法名传入。但实际outputEmitter是RightPanelService的属性(RxJS Subject实例),createSpyObj会把传入的字符串全部创建为spy函数,导致mockPanelService.outputEmitter变成了一个没有pipe方法的普通函数,而非Observable类型,因此调用pipe时触发报错。

解决办法

以下两种方式均可修复:

方法1:手动添加正确的outputEmitter属性

先创建仅包含方法的spy对象,再手动为其添加outputEmitter属性并赋值为RxJS Subject(或Observable):

beforeEach(async () => {
  // 只创建方法的spy,不包含outputEmitter
  mockPanelService = jasmine.createSpyObj(['showLoading', 'create']);
  // 手动添加属性,用Subject模拟真实服务的outputEmitter
  mockPanelService.outputEmitter = new Subject();
  
  await TestBed.configureTestingModule({
    declarations: [RoomConfigurationListComponent],
    imports: [FormsModule],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    providers: [{
      provide: RightPanelService,
      useValue: mockPanelService
    }]
  }).compileComponents();
  fixture = TestBed.createComponent(RoomConfigurationListComponent);
  component = fixture.componentInstance;
});

方法2:使用Jasmine的属性配置(Jasmine 3.4+)

如果你的Jasmine版本≥3.4,可以直接在创建spy对象时同时指定方法和属性:

beforeEach(async () => {
  mockPanelService = jasmine.createSpyObj(
    'RightPanelService', 
    ['showLoading', 'create'], // 要模拟的方法数组
    { outputEmitter: new Subject() } // 要模拟的属性对象
  );
  
  await TestBed.configureTestingModule({
    // 其余配置不变
  }).compileComponents();
  // 其余代码不变
});

如果不需要实际执行订阅逻辑,也可以用of()创建空Observable替代Subject:

mockPanelService.outputEmitter = of(null);

内容的提问来源于stack exchange,提问作者Sandip - Frontend Developer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 22:15:36