【问题标题】:Karma showing this error TypeError: Cannot read property 'textContent' of undefined业力显示此错误 TypeError: Cannot read property 'textContent' of undefined
【发布时间】:2021-05-22 17:46:40
【问题描述】:

我正在编写一个非常基本的单元测试。我不知道该怎么办。我已经尝试了很多东西,但无法解决这个错误。除了第一次测试,其他测试都失败了。

规格文件

fdescribe("New TestOrder ICD10 Code Selection Modal Component", () => {
  let component: NewTestOrderICD10CodeSelectionModalComponent;
  let fixture: ComponentFixture<NewTestOrderICD10CodeSelectionModalComponent>;
  let de: DebugElement;
  let element: HTMLElement;

  beforeEach(async (() => {
    TestBed.configureTestingModule({
      declarations: [
        NewTestOrderICD10CodeSelectionModalComponent
      ],
      imports: [
        ReactiveFormsModule,
        NgbModule.forRoot(),
        FormsModule,
        RouterTestingModule,
        StoreModule.forRoot({}),
        HttpModule
      ],
      providers: [
        AuthService,
        UtilService,
        SessionService,
        TestOrderService,
        {provide: APP_BASE_HREF, useValue : '/'}
      ],
      schemas: [
        NO_ERRORS_SCHEMA
      ]
    }).compileComponents();
  }));

  beforeEach(async() => {
    fixture = TestBed.createComponent(NewTestOrderICD10CodeSelectionModalComponent);
    component = fixture.debugElement.componentInstance;
    de = fixture.debugElement.query(By.css('.new-test-order-icd10-code-selection-modal.component'));
    element  = de.nativeElement;
    fixture.detectChanges();
  });

  it("New Test Order ICD10 Coed Selection Modal Should be created", () => {
    expect(component).toBeTruthy();
  });

  it('Should have a title', () => {
    expect(element.textContent).toContain(component.title);
  });
});

这里是 mew-test-order-icd10-code-selection-modal.component.ts 文件

// Angular DOM
import {Component, ViewChild, TemplateRef, OnDestroy} from "@angular/core";
// Angular Hooks
import {OnInit, AfterViewInit} from "@angular/core";
// Redux Store
import {Store} from "@ngrx/store";
import {SetTestOrderCodeSelectionSession} from "../../../../shared";
// Models
import {
  TestOrder,
  TestOrderCodeSelectionSession
} from "../../../../shared/models";
// Third Party Services
import {NgbModal, NgbModalOptions} from "@ng-bootstrap/ng-bootstrap";
import {ActivatedRoute, Router} from "@angular/router";
import * as lodash from "lodash";
// Services
import {TestOrderService} from "../../../../shared";
import {SetTestOrderICDCodes} from "../../../../shared/actions/test-order.actions";
import {Subscription} from "rxjs/Subscription";

@Component({
  selector: "app-new-test-order-icd10-code-selection-modal",
  templateUrl: "./new-test-order-icd10-code-selection-modal.component.html",
  styleUrls: ["./new-test-order-icd10-code-selection-modal.component.scss"]
})
export class NewTestOrderICD10CodeSelectionModalComponent
  implements OnInit, AfterViewInit, OnDestroy {
  @ViewChild("icd10codes") template: TemplateRef<any>;
  public codeSelectionSession: TestOrderCodeSelectionSession;
  public icdCodes: Array<ICDCode>;
  public newIcdCodesArray = [];
  public modalRef: any;
  title: string = "Please Select ICD-10 Codes";
  public serviceSubscription: Subscription;
  private selectedCodes: Array<ICDCode> = [];
  private options: NgbModalOptions = {
    backdrop: "static",
    windowClass: "icd10-codes",
    container: "app-new-test-order-icd10-code-selection-modal",
    keyboard: false,
    size: "lg"
  };

  constructor(
    private modalService: NgbModal,
    private testOrderService: TestOrderService,
    public router: Router,
    private route: ActivatedRoute,
    private store: Store<any>
  ) {
  }

  ngOnInit() {
    this.serviceSubscription = this.testOrderService.getICDCodes().subscribe((codes: Array<ICDCode>) => {
      this.icdCodes = codes;
    });
    this.store
      .select("codeSelectionSession")
      .subscribe((session: TestOrderCodeSelectionSession) => {
        // checks for null and undefined
        if (session == null) {
          this.store.select("testOrder").subscribe((testOrder: TestOrder) => {
            this.codeSelectionSession = new TestOrderCodeSelectionSession();
            this.store.dispatch(
              new SetTestOrderCodeSelectionSession(this.codeSelectionSession)
            );
          });
        } else {
          this.codeSelectionSession = session;
        }
      });
  }

  ngAfterViewInit() {
    setTimeout(() => {
      this.modalRef = this.modalService.open(this.template, this.options);
    });
    this.serviceSubscription = this.route.queryParams.subscribe(params => {
      //for selected icd
      if(params.selectedIcdCodes) {
        this.selectedCodes = JSON.parse(params.selectedIcdCodes);
      }
      this.codeSelectionSession.SelectedCodes = lodash.concat(this.codeSelectionSession.SelectedCodes, this.selectedCodes);
      //for icd id
      this.codeSelectionSession.ICDCodeIds = lodash.concat(this.codeSelectionSession.ICDCodeIds, params.icd10Codes);
      this.newIcdCodesArray = lodash.concat(this.newIcdCodesArray, params.icd10Codes);

      this.codeSelectionSession.ICDCodeIds = lodash.uniq(this.codeSelectionSession.ICDCodeIds);

      let difference = lodash.difference(this.codeSelectionSession.ICDCodeIds, this.newIcdCodesArray);
      this.codeSelectionSession.ICDCodeIds = lodash.differenceBy(this.codeSelectionSession.ICDCodeIds, difference);  //remove the difference
    });
  }

  onCheckboxUpdate(selected: boolean, icdCode: ICDCode) {
    this.codeSelectionSession.ICDCodeIds = this.codeSelectionSession.ICDCodeIds.filter(
      codeId => codeId !== icdCode.Id
    );
    this.codeSelectionSession.SelectedCodes = this.codeSelectionSession.SelectedCodes.filter(
      code => code.Id !== icdCode.Id
    );
    if (selected) {
      this.codeSelectionSession.ICDCodeIds.push(icdCode.Id);
      this.codeSelectionSession.SelectedCodes.push(icdCode);
    }
  }

  ngOnDestroy() {
    setTimeout(() => {
      this.modalRef.close();
    });
    this.serviceSubscription.unsubscribe();
  }
}

第二个测试也应该通过。我不知道我在这里做错了什么。

我尝试了另一种方法。 这是我的规范文件,它显示我无法读取 null 的属性“nativeElement”

beforeEach(async() => {
    fixture = TestBed.createComponent(NewTestOrderICD10CodeSelectionModalComponent);
    component = fixture.debugElement.componentInstance;
//    de = fixture.debugElement.query(By.css('.h5')).nativeElement.innerText;
//    element  = de.nativeElement;
    fixture.detectChanges();
  });

  it("New Test Order ICD10 Coed Selection Modal Should be created", () => {
    expect(component).toBeTruthy();
  });

  it('Should have a title', () => {
    //expect(element.textContent).toContain(component.title);
    fixture.detectChanges();
    const de = fixture.debugElement.query(By.css('.modal-title')).nativeElement.innerText;
    expect(de).toContain(component.title);
  });

这是我的html文件

<ng-template #icd10codes let-c="close" let-d="dismiss">
  <form role="form" #icdCodeSelectionForm="ngForm" novalidate>
    <div class="modal-header">
      <span class="material-icons clickable text-dimmed" (click)="onBackButtonClick()" [routerLink]="['/test-order/create/details']">arrow_back</span>
      <h5 class="modal-title">{{title}}</h5>
      <button type="button" class="btn square-btn" (click)="update()">ADD CODES</button>
      <div class="icd-search">
        <app-search-list (onSearchInputUpdate)="onSearchUpdate($event)"></app-search-list>
      </div>
    </div>
    <div class="modal-body">
      <div class="row">
        <div class="col-3 text-dimmed pad-left-45">ICD-10 Codes</div>
        <div class="col-8 text-dimmed">Description</div>
        <div class="col-1"></div>
      </div>
      <div class="list-border"></div>
      <div class="code-list">
        <div class="row" *ngFor="let icdCode of icdCodes; let odd = odd" [ngClass]="{'isOdd': odd}">
          <div class="col-3 pad-left-45">{{ icdCode.Code }}</div>
          <div class="col-8 text-dimmed">{{ icdCode.ShortDescription }}</div>
          <div class="col-1">
            <label class="custom-control custom-checkbox">
                <input
                  type="checkbox"
                  class="custom-control-input"
                  [ngModel]="codeSelectionSession.ICDCodeIds.indexOf(icdCode.Id) !== -1"
                  (ngModelChange)="onCheckboxUpdate($event, icdCode)"
                  [name]="icdCode.Id">
                <span class="custom-control-indicator"></span>
            </label>
          </div>
        </div>
      </div>
    </div>
  </form>
</ng-template>

【问题讨论】:

  • 它没有找到 element.textContent 因为你的线程(异步)在 element.textContent 中有任何值之前运行
  • @ShashankVivek 我试过但它仍然无法正常工作可能是因为我的 html 文件我在 ng-template 中关闭了它?
  • @SalmanAhmad :在 html 上呈现 #icd10codes 的条件是什么?请更新帖子中的整个 html 代码和 cmets 中的 not :)。 ng-template 默认不呈现。所以,你的测试用例找不到它
  • @ShashankVivek 感谢您花时间用我的 html 代码更新它:)
  • @ShashankVivek 是的,这是完整的 html 文件。实际上,这里发生的是这个组件 ng-template 仅在您单击其父组件上的组件字段时才会生效。因此,当我测试它时,它实际上并没有加载组件,这就是现在在尝试您描述的编写测试方法之后它显示此错误的原因预期未定义包含请选择 ICD-10 代码我想我必须以不同的方式实现它..我感谢您抽出宝贵的时间研究一下您对我的帮助很大谢谢:)

标签: angular unit-testing jasmine karma-jasmine karma-webpack


【解决方案1】:

当您在beforeEach() 中执行element = de.nativeElement; 时,element 包含该值。
而且,可以通过

it('Should have a title', () => {
    expect(element).toContain(component.title);
});

de = fixture.debugElement.query(By...... 开始,de 将变为HTMLNode,在此使用nativeElement 将为您提供内容。或者,您可以直接使用de.textContent 来获取元素de 的内容。

Alligator.io Link for Unit testing

【讨论】:

    【解决方案2】:

    我的建议是删除

      de = fixture.debugElement.query(By.css('.new-test-order-icd10-code-selection-modal.component'));
    

    来自beforeEach 块。请注意,您试图在 fixture.detectChanges(); 之前获取此值,所以我认为这不是一个好方法。您必须确保在完成所有角度更改后获得 HTML 值。试试:

    it('Should have a title', () => {
      fixture.detectChanges(); // No need to add this line if you have it in "beforeEach" block
      const de = fixture.debugElement.query(By.css('.new-test-order-icd10-code-selection-modal.component'));
      const element  = de.nativeElement;
      expect(element.textContent).toContain(component.title);
    });
    

    更新

    为了测试ng-template,您必须以不同的方式进行:

    app.component.html

    <div id="title">
        {{title}}
    </div>
    <ng-template #content
                 let-modal
                 id="ng-modal">
      <div class="modal-header dark-modal">
        Header
      </div>
      <div class="justify-content-center flex-column flex-md-row list-inline">
        Body
      </div>
    </ng-template>
    

    app.component.ts

    import { Component, ViewChild, TemplateRef } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      title = 'AngularProj';
      @ViewChild('content') modalRef: TemplateRef<any>;
    }
    

    你需要用稍微不同的方式写spec文件:

    app.component.spec.ts

    import { TestBed, async, ComponentFixture } from '@angular/core/testing';
    import { AppComponent } from './app.component';
    import { ViewChild, Component, OnInit, AfterContentInit, TemplateRef } from '@angular/core';
    import { By } from '@angular/platform-browser';
    
    @Component({
      template: `
        <ng-container *ngTemplateOutlet="modal"> </ng-container>
        <app-root></app-root>
      `,
    })
    class WrapperComponent implements AfterContentInit {
      @ViewChild(AppComponent) appComponentRef: AppComponent;
      modal: TemplateRef<any>;
      ngAfterContentInit() {
        this.modal = this.appComponentRef.modalRef;
      }
    }
    
    describe('AppComponent', () => {
      let app: AppComponent;
      let fixture: ComponentFixture<WrapperComponent>;
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [WrapperComponent, AppComponent],
        }).compileComponents();
      }));
      beforeEach(() => {
        fixture = TestBed.createComponent(WrapperComponent);
        const wrapperComponent = fixture.debugElement.componentInstance;
        app = wrapperComponent.appComponentRef;
        fixture.detectChanges();
      });
      it('should create the app', async(() => {
        expect(app).toBeDefined();
      }));
      it('should have title in HtmL ', async(() => {
        const titleText = (fixture.debugElement.nativeElement.querySelector('#title').innerText);
        expect(titleText).toBe('AngularProj');
      }));
      it('should have Header in HtmL ', async(() => {
        const headerText = (fixture.debugElement.queryAll(By.css('.modal-header.dark-modal'))[0].nativeElement.innerText);
        expect(headerText).toBe('Header');
      }));
    });
    

    1. 如您所见,我用示例测试组件 (WrapperComponent) 包装了 app-root
    2. 因为app-rootng-template,所以它不会自己渲染。这造成了一个棘手的情况,因为我们需要渲染 app.component 的这一部分。
    3. 通过创建@ViewChild('content') modalRef: TemplateRef&lt;any&gt;; 公开ng-template,然后使用它在WrapperComponent 内部进行渲染。

    我知道这似乎是一种 hack,但在我浏览过的所有文章中,这就是我们实现这一目标的方法。

    【讨论】:

      猜你喜欢
      • 2020-09-20
      • 2021-06-25
      • 2019-04-05
      • 1970-01-01
      • 2017-04-24
      • 2023-03-15
      • 2019-12-08
      • 2020-11-07
      • 2020-10-25
      相关资源
      最近更新 更多