Angular Unit Test - TypeError: Cannot read property 'name' of undefined
Angular Unit Test - TypeError: Cannot read property 'name' of undefined
我正在 运行 Angular 单元测试 Jasmine/Karma。目前出现以下错误:
TypeError: Cannot read property 'name' of undefined
at ReplicateListComponent_Template (ng:///ReplicateListComponent.js:221:64)
at executeTemplate (http://localhost:9876/_karma_webpack_/vendor.js:49857:9)
at refreshView (http://localhost:9876/_karma_webpack_/vendor.js:49723:13)
at refreshComponent (http://localhost:9876/_karma_webpack_/vendor.js:50894:13)
at refreshChildComponents (http://localhost:9876/_karma_webpack_/vendor.js:49520:9)
ReplicateListComponent.js 的第 221 行:
jit___property_12('title',jit___pipeBind1_9(13,28,'upload_replicate_tooltip'))('routerLink',
jit___pureFunction1_10(32,_c1,ctx.managementConfig.name));
jit___advance_6(3);
ctx.managementConfig.name
- ctx 没有名为 managementConfig
的变量 - 请参阅屏幕截图。
单元测试代码:
let component: ReplicateViewComponent;
let fixture: ComponentFixture<ReplicateViewComponent>;
let params: Subject<Params>;
let queryParams: Subject<Params>;
let mockManagementService: jasmine.SpyObj<ManagementConfigService>;
let getManagementConfig: Subject<IManagementConfig>;
let environmentSwitched: Subject<string>;
beforeEach(async () => {
params = new Subject<Params>();
queryParams = new Subject<Params>();
mockManagementService = jasmine.createSpyObj<ManagementConfigService>([ 'getManagementConfig', 'getCurrentManagementConfig', 'findManagementConfig', 'environmentSwitched' ]);
getManagementConfig = new Subject<IManagementConfig>();
environmentSwitched = new Subject<string>();
...
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
ToastrModule.forRoot({}),
RouterTestingModule.withRoutes([
{ path: ':env/replicates', component: DummyComponent },
]),
...
],
declarations: [
ReplicateViewComponent,
...
ReplicateListComponent,
...
],
providers: [
{ provide: ManagementConfigService, useValue: mockManagementService },
{ provide: ActivatedRoute, useValue: ACTIVATED_ROUTE },
{ provide: RuntimeConfig, useValue: RUNTIME_CONFIG },
...
],
})
.compileComponents();
mockManagementService.getManagementConfig.and.returnValue(getManagementConfig);
mockManagementService.getCurrentManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
mockManagementService.findManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
mockManagementService.environmentSwitched.and.returnValue(environmentSwitched);
});
beforeEach(() => {
fixture = TestBed.createComponent(ReplicateViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.list.managementConfig = RUNTIME_CONFIG.environments[0];
component.list.replicates = REPLICATIONS;
component.details.replicate = REPLICATIONS[0];
const router = TestBed.inject(Router);
router.initialNavigation();
getManagementConfig.next(RUNTIME_CONFIG.environments[0]);
environmentSwitched.next(RUNTIME_CONFIG.environments[0].name);
params.next({ id: '11111111-1111-1111-1111-111111111111' });
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component.list).toBeTruthy();
expect(component.details).toBeTruthy();
});
为什么我希望有一个名为 managementConfig
的变量:
export class ReplicateListComponent extends BaseContainerComponent implements OnInit {
managementConfig: IManagementConfig;
...
ngOnInit(): void {
this.subs.add(
this.route.params.subscribe(
(params: Params) => {
this.managementConfig = this.managementService.findManagementConfig(params.env);
ReplicateListComponent 的违规部分 HTML - 请参阅 routerLink
<div class="my-auto ml-auto">
<a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
<i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
</a>
</div>
ReplicateListComponent.js 与 AssetListComponent.js 非常相似,没有错误。从屏幕截图中可以看出,在 AssetListComponent:
的上下文中有一个 managementConfig
AssetListComponent的routerLinkHTML本质上是一样的:
<a
data-cy="uploadLink"
[title]="'upload_asset_tooltip' | translate"
[routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
[queryParamsHandling]="'preserve'"
*ngIf="managementConfig?.upload"
>
<i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
</a>
AssetListComponent后端代码:
export class AssetListComponent extends BaseContainerComponent implements OnInit {
managementConfig: IManagementConfig;
...
ngOnInit(): void {
this.subs.add(
this.route.params.subscribe(
(params: Params) => {
this.managementConfig = this.managementService.findManagementConfig(params.env);
- 错误原因是什么?
- 修复方法是什么?
- 为什么是ReplicateListComponent错误,AssetListComponent却没有?
谢谢。
1.) 错误是因为您调用的第一个 fixture.detectChanges()
,managementConfig
在那个时间点未定义。你的另一个组件,有一个 *ngIf
没有发生这个问题,它将在 #3 中解释。
2.) 解决方法是隐藏此 HTML 直到 managementConfig
为真:
<div class="my-auto ml-auto" *ngIf="managementConfig">
<a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
<i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
</a>
</div>
3.) 错误在一个地方而不是另一个地方,因为在第二个地方你有一个 *ngIf
:
*ngIf="managementConfig?.upload"
首先检查managementConfig
是否存在(带问号),如果存在,对象中是否存在upload
,如果存在,继续显示这个 HTML 标记。
如果你删除这个*ngIf
,我想这里也会发生错误。
<a
data-cy="uploadLink"
[title]="'upload_asset_tooltip' | translate"
[routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
[queryParamsHandling]="'preserve'"
*ngIf="managementConfig?.upload"
>
<i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
</a>
我正在 运行 Angular 单元测试 Jasmine/Karma。目前出现以下错误:
TypeError: Cannot read property 'name' of undefined
at ReplicateListComponent_Template (ng:///ReplicateListComponent.js:221:64)
at executeTemplate (http://localhost:9876/_karma_webpack_/vendor.js:49857:9)
at refreshView (http://localhost:9876/_karma_webpack_/vendor.js:49723:13)
at refreshComponent (http://localhost:9876/_karma_webpack_/vendor.js:50894:13)
at refreshChildComponents (http://localhost:9876/_karma_webpack_/vendor.js:49520:9)
ReplicateListComponent.js 的第 221 行:
jit___property_12('title',jit___pipeBind1_9(13,28,'upload_replicate_tooltip'))('routerLink',
jit___pureFunction1_10(32,_c1,ctx.managementConfig.name));
jit___advance_6(3);
ctx.managementConfig.name
- ctx 没有名为 managementConfig
的变量 - 请参阅屏幕截图。
单元测试代码:
let component: ReplicateViewComponent;
let fixture: ComponentFixture<ReplicateViewComponent>;
let params: Subject<Params>;
let queryParams: Subject<Params>;
let mockManagementService: jasmine.SpyObj<ManagementConfigService>;
let getManagementConfig: Subject<IManagementConfig>;
let environmentSwitched: Subject<string>;
beforeEach(async () => {
params = new Subject<Params>();
queryParams = new Subject<Params>();
mockManagementService = jasmine.createSpyObj<ManagementConfigService>([ 'getManagementConfig', 'getCurrentManagementConfig', 'findManagementConfig', 'environmentSwitched' ]);
getManagementConfig = new Subject<IManagementConfig>();
environmentSwitched = new Subject<string>();
...
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
ToastrModule.forRoot({}),
RouterTestingModule.withRoutes([
{ path: ':env/replicates', component: DummyComponent },
]),
...
],
declarations: [
ReplicateViewComponent,
...
ReplicateListComponent,
...
],
providers: [
{ provide: ManagementConfigService, useValue: mockManagementService },
{ provide: ActivatedRoute, useValue: ACTIVATED_ROUTE },
{ provide: RuntimeConfig, useValue: RUNTIME_CONFIG },
...
],
})
.compileComponents();
mockManagementService.getManagementConfig.and.returnValue(getManagementConfig);
mockManagementService.getCurrentManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
mockManagementService.findManagementConfig.and.returnValue(RUNTIME_CONFIG.environments[0]);
mockManagementService.environmentSwitched.and.returnValue(environmentSwitched);
});
beforeEach(() => {
fixture = TestBed.createComponent(ReplicateViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.list.managementConfig = RUNTIME_CONFIG.environments[0];
component.list.replicates = REPLICATIONS;
component.details.replicate = REPLICATIONS[0];
const router = TestBed.inject(Router);
router.initialNavigation();
getManagementConfig.next(RUNTIME_CONFIG.environments[0]);
environmentSwitched.next(RUNTIME_CONFIG.environments[0].name);
params.next({ id: '11111111-1111-1111-1111-111111111111' });
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component.list).toBeTruthy();
expect(component.details).toBeTruthy();
});
为什么我希望有一个名为 managementConfig
的变量:
export class ReplicateListComponent extends BaseContainerComponent implements OnInit {
managementConfig: IManagementConfig;
...
ngOnInit(): void {
this.subs.add(
this.route.params.subscribe(
(params: Params) => {
this.managementConfig = this.managementService.findManagementConfig(params.env);
ReplicateListComponent 的违规部分 HTML - 请参阅 routerLink
<div class="my-auto ml-auto">
<a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
<i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
</a>
</div>
ReplicateListComponent.js 与 AssetListComponent.js 非常相似,没有错误。从屏幕截图中可以看出,在 AssetListComponent:
的上下文中有一个 managementConfigAssetListComponent的routerLinkHTML本质上是一样的:
<a
data-cy="uploadLink"
[title]="'upload_asset_tooltip' | translate"
[routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
[queryParamsHandling]="'preserve'"
*ngIf="managementConfig?.upload"
>
<i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
</a>
AssetListComponent后端代码:
export class AssetListComponent extends BaseContainerComponent implements OnInit {
managementConfig: IManagementConfig;
...
ngOnInit(): void {
this.subs.add(
this.route.params.subscribe(
(params: Params) => {
this.managementConfig = this.managementService.findManagementConfig(params.env);
- 错误原因是什么?
- 修复方法是什么?
- 为什么是ReplicateListComponent错误,AssetListComponent却没有?
谢谢。
1.) 错误是因为您调用的第一个 fixture.detectChanges()
,managementConfig
在那个时间点未定义。你的另一个组件,有一个 *ngIf
没有发生这个问题,它将在 #3 中解释。
2.) 解决方法是隐藏此 HTML 直到 managementConfig
为真:
<div class="my-auto ml-auto" *ngIf="managementConfig">
<a data-cy="batchReplicateLink" [title]="'upload_replicate_tooltip' | translate" [routerLink]="[ '/', managementConfig.name, 'replicates', 'batch']" queryParamsHandling="preserve">
<i class="icons8-sm icons8-Copy"></i>{{ 'replicate_batch_link' | translate }}
</a>
</div>
3.) 错误在一个地方而不是另一个地方,因为在第二个地方你有一个 *ngIf
:
*ngIf="managementConfig?.upload"
首先检查managementConfig
是否存在(带问号),如果存在,对象中是否存在upload
,如果存在,继续显示这个 HTML 标记。
如果你删除这个*ngIf
,我想这里也会发生错误。
<a
data-cy="uploadLink"
[title]="'upload_asset_tooltip' | translate"
[routerLink]="[ '/', managementConfig.name, 'assets', 'upload']"
[queryParamsHandling]="'preserve'"
*ngIf="managementConfig?.upload"
>
<i class="icons8-sm icons8-Upload"></i>{{ 'asset_list_upload_link' | translate }}
</a>