测试使用自定义DatePipe的Angular组件时遇构造函数调用错误
Angular单元测试中继承DatePipe的自定义管道报错:TypeError: Class constructor DatePipe cannot be invoked without 'new'
我自定义了一个继承自DatePipe的FormatTimePipe管道,用来把字符串转换成指定日期格式,代码如下:
// FormateTimePipe.ts import { Pipe, PipeTransform } from '@angular/core'; import { DatePipe } from '@angular/common'; @Pipe({ name: 'time' }) export class FormatTimePipe extends DatePipe implements PipeTransform { override transform(value: any, args?: any): any { value = `${new Date().toDateString()} ${value.replaceAll('.', ':')}`; return super.transform(value, args); } }
接着我给用到这个自定义管道的AmendmentListDropdownComponent写了单元测试,测试代码如下:
// amendment-list-dropdown-spec.ts import { FormatTimePipe } from '@agic/shared/pipes/formateTime.pipe'; import { PadZeroPipe } from '@agic/shared/pipes/padZero.pipe'; import { DatePipe } from '@angular/common'; import { DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { CustomDropdownComponent } from '@motorcycle/components/common/custom-dropdown/custom-dropdown.component'; import { AmendmentListDropdownComponent } from './amendment-list-dropdown.component'; export function findComponent<T>(fixture: ComponentFixture<T>, selector: string): DebugElement { return fixture.debugElement.query(By.css(selector)); } describe('AmendmentListDropdownComponent', () => { let component: AmendmentListDropdownComponent; let fixture: ComponentFixture<AmendmentListDropdownComponent>; const padZeroPipe: PadZeroPipe = new PadZeroPipe(); beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AmendmentListDropdownComponent, CustomDropdownComponent, PadZeroPipe, FormatTimePipe ] }).compileComponents(); fixture = TestBed.createComponent(AmendmentListDropdownComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should bind the configured value', () => { const customDD = findComponent(fixture, 'custom-dropdown'); const customDDComponent = customDD.componentInstance; const display = customDD.nativeElement.querySelector('.dropdown-button span.dropdown-value span'); const listItem = component.amendmentList.data[0]; expect(display.textContent).toBe(`Amendment: ${padZeroPipe.transform(listItem.attributes.amendmentSequence)} ${listItem.attributes.amendmentType} : ${listItem.attributes.amendmentTypeDescription}`); customDDComponent.currentValue = component.amendmentList.data[1]; fixture.detectChanges(); fixture.whenStable().then(() => { const selected = customDDComponent.currentValue; expect(display.textContent).toBe(`Amendment: ${padZeroPipe.transform(selected.attributes.amendmentSequence)} ${selected.attributes.amendmentType} : ${selected.attributes.amendmentTypeDescription}`); }); }); });
运行测试时出现以下错误:
TypeError: Class constructor DatePipe cannot be invoked without 'new' at new FormatTimePipe (packages/shared/pipes/formateTime.pipe.ts:351:43) at FormatTimePipe_Factory (ng:\FormatTimePipe\ɵfac.js:5:10) at ɵɵpipe (node_modules/@angular/core/fesm2020/core.mjs:22715:30) at AmendmentListDropdownComponent_ng_template_3_div_1_Template (ng:/AmendmentListDropdownComponent.js:36:5) at executeTemplate (node_modules/@angular/core/fesm2020/core.mjs:12121:9) at renderView (node_modules/@angular/core/fesm2020/core.mjs:11921:13) at TemplateRef2.createEmbeddedView (node_modules/@angular/core/fesm2020/core.mjs:23143:9) at ViewContainerRef2.createEmbeddedView (node_modules/@angular/core/fesm2020/core.mjs:23261:37) at node_modules/@angular/common/fesm2020/common.mjs:3225:31 at DefaultIterableDiffer.forEachOperation (node_modules/@angular/core/fesm2020/core.mjs:28558:17) at NgForOf._applyChanges (node_modules/@angular/common/fesm2020/common.mjs:3220:17)
问题原因
DatePipe是Angular的内置管道,它依赖LOCALE_ID等注入参数。通过继承DatePipe的方式自定义管道时,Angular的依赖注入系统无法自动为父类DatePipe提供所需的构造参数,导致实例化FormatTimePipe时无法正确调用DatePipe的构造函数,从而抛出该错误。
解决方法
方法一:修改自定义管道实现,改为注入DatePipe而非继承
不直接继承DatePipe,而是通过依赖注入的方式使用DatePipe,这样Angular会自动处理其依赖参数:
// FormateTimePipe.ts import { Pipe, PipeTransform, Inject } from '@angular/core'; import { DatePipe, LOCALE_ID } from '@angular/common'; @Pipe({ name: 'time' }) export class FormatTimePipe implements PipeTransform { constructor( private datePipe: DatePipe, @Inject(LOCALE_ID) private locale: string ) {} transform(value: any, args?: any): any { value = `${new Date().toDateString()} ${value.replaceAll('.', ':')}`; // 使用注入的datePipe实例调用transform方法,传入locale参数 return this.datePipe.transform(value, args, this.locale); } }
方法二:在测试模块中提供DatePipe所需的依赖
如果坚持继承DatePipe,需要在测试模块中为DatePipe提供必要的依赖(比如LOCALE_ID),并确保DatePipe被正确提供:
修改测试模块的配置:
beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ AmendmentListDropdownComponent, CustomDropdownComponent, PadZeroPipe, FormatTimePipe ], providers: [ // 提供LOCALE_ID的默认值 { provide: LOCALE_ID, useValue: 'en-US' }, DatePipe ] }).compileComponents(); // ... 后续代码不变 });
注意事项
推荐使用方法一,因为继承Angular内置管道可能会受版本更新影响,而通过依赖注入使用内置管道的方式更符合Angular的DI设计原则,也能避免类似的构造函数调用问题。
内容的提问来源于stack exchange,提问作者Tessy Thomas
相关产品推荐
相关产品推荐

