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);

  1. 错误原因是什么?
  2. 修复方法是什么?
  3. 为什么是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>