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

测试使用自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:14:56