如何用Jest测试继承带泛型基类的Angular组件?
问题描述
我有如下基类:
export class TableBase<TData> implements OnInit, OnDestroy, AfterViewInit, OnDestroy { @ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>; }
以及如下组件:
export class MasterdataGlobalComponent extends TableBase<IMasterdataGlobalItem> { @ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>; }
其他测试均正常运行,但遇到如下错误,推测是TData对象相关问题:
NullInjectorError: R3InjectorError(Standalone[MasterdataGlobalComponent])[[object Object] -> [object Object] -> [object Object]]: NullInjectorError: No provider for [object Object]!
请问定义/传递该对象的最佳方式是什么?
以下是当前的测试代码:
jest.mock('../../../framework/common/services/auth.service'); import { AuthenticationService } from '../../../framework/common/services/auth.service'; import { ArticleService } from '../../../app/inventory/articles/article.service'; import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core'; import { ToastrModule } from 'ngx-toastr'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { MockAuthenticationService, MockMasterdataGlobalItem } from '../../mocks/mocks'; import { MasterdataGlobalComponent } from './masterdata-global.component'; import { MasterdataGlobalItemsService } from './masterdata-global-items-service'; import { TableBase } from '../../../framework/common/table/table-base'; import { IMasterdataGlobalItem } from './masterdata-global-item.interface'; import { NgbdSortableHeader } from '../../../framework/common/table/sortable.directive'; // jest.mock('../../../framework/common/services/auth.service', () => ({ // AuthenticationService: { // currentUser$: new BehaviorSubject<any>({ // config: [ // { setting: 'rowsPerPage', value: '10' } // Adjust as needed // ] // }) // } // })); class MockTableBase extends TableBase<any> { constructor() { super(null, null, '', null); } // Override methods or properties used by MasterdataGlobalComponent here // For example: ngOnInit() {} ngOnDestroy() {} // Implement other methods or properties as needed } // Mocking the PlantSwitched observable const mockPlantSwitched = new BehaviorSubject<any>(null); // Mocking the UserPlantsService jest.mock('../../../framework/common/services/user-plants.service', () => ({ UserPlantsService: jest.fn().mockImplementation(() => ({ activatedUserPlant$: new BehaviorSubject<any>(null) })) })); jest.mock('../../../app/inventory/articles/article.service', () => ({ ArticleService: { // Mocked methods or properties as needed } })); // jest.mock('../../../framework/common/table/table-base', () => ({ // TableBase: MockTableBase // })); // Mock data for testing const mockData: IMasterdataGlobalItem[] = [ // Define mock items here ]; describe('MasterdataGlobalComponent', () => { let component: MasterdataGlobalComponent; let fixture: ComponentFixture<MasterdataGlobalComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ MasterdataGlobalComponent, NgbdSortableHeader, HttpClientTestingModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), ToastrModule.forRoot() ], providers: [ { provide: AuthenticationService, useClass: MockAuthenticationService }, provideMockStore({}), { provide: MasterdataGlobalItemsService, useValue: MasterdataGlobalItemsService }, { provide: TableBase, useClass: MockTableBase } ] }).compileComponents(); })); beforeEach(() => { (window as any).PlantSwitched = mockPlantSwitched; const dataSubject = new BehaviorSubject<IMasterdataGlobalItem[]>([]); Object.defineProperty(MasterdataGlobalComponent.prototype, 'data$', { get: jest.fn(() => dataSubject.asObservable()) }); fixture = TestBed.createComponent(MasterdataGlobalComponent); component = fixture.componentInstance; fixture.detectChanges(); // Set the mock data dataSubject.next(mockData); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
这个基类是该组件与其他测试正常组件的唯一区别。
解决方案
首先明确:错误和TData泛型本身无关——泛型是类型层面的定义,运行时不会存在。问题核心在于基类TableBase的依赖注入配置错误,以及测试中的几个不当设置,以下是修复步骤:
1. 清理测试中错误的Provider配置
- 移除
providers中的{ provide: TableBase, useClass: MockTableBase }:子类继承基类时,Angular会自动解析基类的依赖,单独注册基类Provider会导致注入冲突。 - 修正
MasterdataGlobalItemsService的Provider:你当前用useValue: MasterdataGlobalItemsService是把类本身作为值传递,应该传入mock实例:{ provide: MasterdataGlobalItemsService, useValue: { // 模拟组件需要的服务方法,比如: getGlobalItems: jest.fn().mockReturnValue(new BehaviorSubject<IMasterdataGlobalItem[]>(mockData).asObservable()) } }
2. 正确模拟基类的依赖与行为
如果TableBase的构造函数需要依赖(比如你代码中super(null, null, '', null)暗示的4个参数),要在测试中为这些依赖提供对应的mock:
- 若依赖是Angular内置服务(如
HttpClient),已由HttpClientTestingModule提供,无需额外处理; - 若依赖是自定义服务,需在
providers中添加对应的mock。
另外,可直接用jest.mock替换整个TableBase,避免手动继承模拟:
jest.mock('../../../framework/common/table/table-base', () => ({ TableBase: jest.fn().mockImplementation(() => ({ headers: new QueryList<NgbdSortableHeader>(), ngOnInit: jest.fn(), ngOnDestroy: jest.fn(), ngAfterViewInit: jest.fn() })) }));
3. 完善泛型相关的测试数据
虽然泛型不影响运行,但为了测试完整性,补充符合IMasterdataGlobalItem类型的mock数据:
const mockData: IMasterdataGlobalItem[] = [ { id: 1, name: '测试项1' }, { id: 2, name: '测试项2' } ];
根据你的实际接口字段调整内容。
4. 修正其他测试细节
- 移除
imports中的NgbdSortableHeader:指令不需要直接导入到测试模块,若它属于某个共享模块,导入该模块即可; - 去掉
Object.defineProperty对data$的覆盖:如果data$是从MasterdataGlobalItemsService获取,通过mock服务返回数据即可,无需手动覆盖原型属性。
修正后的核心测试配置示例
beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ MasterdataGlobalComponent, HttpClientTestingModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), ToastrModule.forRoot() ], providers: [ { provide: AuthenticationService, useClass: MockAuthenticationService }, provideMockStore({}), { provide: MasterdataGlobalItemsService, useValue: { getGlobalItems: jest.fn().mockReturnValue(new BehaviorSubject<IMasterdataGlobalItem[]>(mockData).asObservable()) } } ] }).compileComponents(); })); beforeEach(() => { (window as any).PlantSwitched = mockPlantSwitched; fixture = TestBed.createComponent(MasterdataGlobalComponent); component = fixture.componentInstance; fixture.detectChanges(); });
内容的提问来源于stack exchange,提问作者nokiko
相关产品推荐
相关产品推荐

