因果报应显示此错误TypeError:无法读取未定义的属性'textContent'

时间:2019-03-28 07:19:00

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

我正在编写一个非常基本的单元测试。我不知道该怎么办。我尝试了很多事情,但无法解决此错误。除第一次测试外,其他测试均失败。

规范文件

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

第二项测试也应该通过。我不知道我在做什么错。

我尝试过的另一种方法。 这是我的specfile,它向我显示了无法读取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>

2 个答案:

答案 0 :(得分:0)

element = de.nativeElement;中执行beforeEach()时,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

答案 1 :(得分:0)

我的建议是删除

  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. 如您所见,我用示例测试组件(app-root)包装了WrapperComponent
  2. 由于app-root具有ng-template,因此它不会自己呈现。由于我们需要呈现app.component的这一部分,因此这会造成棘手的情况。
  3. 通过创建ng-template暴露@ViewChild('content') modalRef: TemplateRef<any>;,然后使用它在WrapperComponent内部进行渲染。

我知道这似乎是一种hack,但是在我浏览的所有文章中,这都是我们可以实现的方式。