Angular 单元测试 - TypeError:无法读取未定义的属性“名称”

时间:2021-07-09 15:42:52

标签: angular jasmine

我正在使用 Jasmine/Karma 运行 Angular 单元测试。当前收到以下错误:

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 的变量 - 见截图。

enter image description here

单元测试代码:

  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:

enter image description here

AssetListComponent 的 routerLink HTML 本质上是一样的:

      <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 个答案:

答案 0 :(得分:1)

1.) 该错误是因为您调用的第一个 managementConfig *ngIf 在那个时间点未定义。您的另一个组件,有一个 managementConfig,该问题不会发生,将在 #3 中解释。

2.) 解决方法是隐藏此 HTML,直到 <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> 为真:

*ngIf

3.) 错误在一个地方而不是另一个地方,因为在第二个地方你有一个 *ngIf="managementConfig?.upload":

managementConfig 首先检查 upload 是否存在(带问号),如果存在,则 *ngIf 是否存在于对象中,如果存在,继续并显示此 HTML标记。

如果您删除此 <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> ,我认为这里也会发生错误。

/* stylelint-disable no-descending-specificity */
// your styles
/* stylelint-enable no-descending-specificity */