Angular测试报错:Cannot read properties of undefined (reading 'baseURL')
Karma测试HeaderComponent时出现TypeError:无法读取undefined的baseURL属性
运行HeaderComponent的默认Karma测试用例时,抛出TypeError,提示无法读取undefined的baseURL属性。错误根源是DailyplanningService构造函数中调用this.configService.getConfig().baseURL,且多个组件测试均出现此问题。
dailyplanningservice.ts
import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; import { AppConfigService } from 'src/app/services/config/app-config.service'; import { Observable,} from 'rxjs'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { RequisitionProposalPostModel } from '../model/requisition-proposal'; var jsonData = './assets/data/test.json'; interface JsonResponse { status: number; message: any; data: object; dataList:Array<any>; } @Injectable({ providedIn: 'root' }) export class DailyplanningService { private handleResponse(response: JsonResponse) { return response; } serviceBase = ""; baseUrl = ""; constructor(private _http: HttpClient, private configService: AppConfigService) { this.baseUrl = this.configService.getConfig().baseURL; this.serviceBase = this.configService.getConfig().serviceBase; } getReportData(imoCode: string) { return this._http.get<any>(this.baseUrl + this.serviceBase + `dailyplan/noonReport?imoCode=` + imoCode); } getVesselData(imoCode: string) { return this._http.get(this.baseUrl + this.serviceBase + `dailyplan/vesselCard?imoCode=` + imoCode); } getVesselCardData(imoCode: any): Observable<Object> { return this._http.get(this.baseUrl + this.serviceBase + `dailyplan/port?imoCode=` + imoCode) } submitrfqProposal(requisitionProposalPostModel: RequisitionProposalPostModel): Observable<any> { return this._http.post<any>( this.baseUrl + this.serviceBase + 'dailyplan/requisition', requisitionProposalPostModel); } getRequisitionProposal(imoCode:any, voyageId:any): Observable<any> { return this._http.get<any>(this.baseUrl + this.serviceBase + `dailyplan/requisitionproposal?imoCode=` + imoCode +"&voyagePassageId="+voyageId); } // public getVesselData1(imoCode: any) { // return this.http.get(this.baseUrl + this.serviceBase + `dailyplan/vesselCard?imoCode=` + imoCode).pipe( // catchError( // (error: HttpErrorResponse, caught: Observable<HttpEvent<unknown>>) => { // console.error( // 'the component has caught an error, process it here', // error // ); // this.error = error; // return of(); // } // ) // ); // } doRequest(endpointURL: string, method: string, data?: any, parms?: any, headers?: HttpHeaders) { const httpOptions = { headers: headers ? headers : new HttpHeaders({ 'Content-Type': 'application/json', }), body: !data ? {} : JSON.stringify(data), parms: parms } return this._http .request<JsonResponse>(method, this.baseUrl + this.serviceBase + `${endpointURL}`, httpOptions) .pipe( map(response => this.handleResponse(response)) ); } }
HeaderComponent.ts
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { CognitoService } from 'src/app/modules/auth/cognito.service'; import { DailyplanningService } from 'src/app/modules/pages/daily-planning/service/dailyplanning.service'; import { CommonService } from 'src/app/services/common/common.service'; @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'], }) export class HeaderComponent implements OnInit { responseLst: any = []; dailyplanningURL = ""; bell_icon: boolean = false; constructor(public router: Router, private _service: DailyplanningService, private common: CommonService, public cognitoService: CognitoService) { this.common.shareDailyPlaningURL.subscribe((res: any) => { this.dailyplanningURL = res; }); this.common.shareVesselData.subscribe((res: any) => { if (res) { this.responseLst = res; } else { this.getVesselData(); } }); } userAdminUrlActive = ""; loading: boolean = false; collapsed = true; ngOnInit(): void { console.log(); } toggleCollapsed(): void { this.collapsed = !this.collapsed; } goDashboard() { this.dailyplanningURL = ''; this.isShowPopIn = false; this.router.navigate(['/pages/dashboard']); } changeUrl() { this.dailyplanningURL = ''; this.isShowPopIn = false; } logout() { this.cognitoService.signOut(); this.router.navigate(['/login']); } userAccountClick() { this.userAdminUrlActive = '/pages/useraccount'; this.router.navigate(['/pages/useraccount']); } vesselMgtClick() { this.userAdminUrlActive = '/pages/vesselmanagement'; this.router.navigate(['/pages/vesselmanagement']); } gotoDailyplanning(data: any) { this.isShowPopIn = false; sessionStorage.setItem("vesselData", JSON.stringify(data)); this.router.navigateByUrl('/pages/dashboard') .then(() => this.router.navigate(['/pages/dailyplanning'])); this.dailyplanningURL = '/pages/dailyplanning'; } isShowPopIn = false; showdailyPlanning() { if (this.isShowPopIn) { this.isShowPopIn = false; } else { if (this.responseLst.length == 0) { this.getVesselData(); } setTimeout(() => { this.isShowPopIn = true; }, 500); } } getVesselData() { try { this.loading = true; let apiURL = "useradmin/vessels?email=" + sessionStorage.getItem('email') this.common.doRequest(apiURL, 'get').subscribe({ next: (res: any) => { let result: any = res; this.responseLst = []; if (result) { if (result.status == 200) { result.dataList.forEach((item: any) => { let obj: any = new Object(); Object.keys(item).forEach(function (key: string) { if (item[key] == 0 || item[key] == null) { obj[key] = "0"; } else { obj[key] = item[key]; } }); this.responseLst.push(obj); this.loading = false; }); } } }, error: (error: any) => { this.loading = false; console.log('Error in getVesselData:' + error); } }); } catch (error: any) { this.loading = false; console.log('Error in getVesselData:' + error.message); } } }
HeaderComponent.spec.ts
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { HeaderComponent } from './header.component'; describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture<HeaderComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [RouterTestingModule, HttpClientTestingModule], declarations: [ HeaderComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
错误信息
TypeError: Cannot read properties of undefined (reading 'baseURL') TypeError: Cannot read properties of undefined (reading 'baseURL') at new DailyplanningService (http://localhost:9876/_karma_webpack_/webpack:/src/app/modules/pages/daily-planning/service/dailyplanning.service.ts:32:50) at Object.DailyplanningService_Factory [as factory] (ng:///DailyplanningService/ɵfac.js:4:10) at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/vendor.js:256012:35) at R3Injector.get (http://localhost:9876/_karma_webpack_/vendor.js:255831:33) at NgModuleRef.get (http://localhost:9876/_karma_webpack_/vendor.js:266387:33) at Object.get (http://localhost:9876/_karma_webpack_/vendor.js:266064:35) at lookupTokenUsingModuleInjector (http://localhost:9876/_karma_webpack_/vendor.js:247913:39) at getOrCreateInjectable (http://localhost:9876/_karma_webpack_/vendor.js:248025:12) at ɵɵdirectiveInject (http://localhost:9876/_karma_webpack_/vendor.js:258951:12) at NodeInjectorFactory.HeaderComponent_Factory [as factory] (ng:///HeaderComponent/ɵfac.js:4:81)
解决方案
问题核心是测试模块未提供AppConfigService的模拟实现,导致DailyplanningService初始化时configService.getConfig()返回undefined,进而读取baseURL时出错。需在测试配置中添加该服务的模拟,同时处理其他依赖的模拟:
修改测试代码
更新HeaderComponent.spec.ts,添加所有依赖服务的模拟:
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppConfigService } from 'src/app/services/config/app-config.service'; import { CognitoService } from 'src/app/modules/auth/cognito.service'; import { CommonService } from 'src/app/services/common/common.service'; import { of } from 'rxjs'; import { HeaderComponent } from './header.component'; describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture<HeaderComponent>; let mockAppConfigService: jasmine.SpyObj<AppConfigService>; let mockCognitoService: jasmine.SpyObj<CognitoService>; let mockCommonService: jasmine.SpyObj<CommonService>; beforeEach(async () => { // 创建AppConfigService模拟,返回包含baseURL和serviceBase的配置对象 mockAppConfigService = jasmine.createSpyObj('AppConfigService', ['getConfig']); mockAppConfigService.getConfig.and.returnValue({ baseURL: 'http://mock-url.com', serviceBase: 'api/' }); // 模拟CognitoService的signOut方法 mockCognitoService = jasmine.createSpyObj('CognitoService', ['signOut']); // 模拟CommonService的Observable和doRequest方法 mockCommonService = jasmine.createSpyObj('CommonService', ['doRequest']); mockCommonService.shareDailyPlaningURL = of(''); mockCommonService.shareVesselData = of([]); mockCommonService.doRequest.and.returnValue(of({status:200, dataList:[]})); await TestBed.configureTestingModule({ imports: [RouterTestingModule, HttpClientTestingModule], declarations: [ HeaderComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { provide: AppConfigService, useValue: mockAppConfigService }, { provide: CognitoService, useValue: mockCognitoService }, { provide: CommonService, useValue: mockCommonService } ] }) .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
说明
- 给
AppConfigService创建模拟对象,让getConfig返回包含baseURL和serviceBase的模拟配置,解决DailyplanningService初始化时的undefined问题。 - 同时模拟
HeaderComponent依赖的CognitoService和CommonService,避免其他依赖缺失导致的测试失败,确保测试能正常执行。
内容的提问来源于stack exchange,提问作者Mayuri Jadhav
相关产品推荐
相关产品推荐

