【问题标题】:Jasmine - Cannot read property 'controls' of undefined - Angular 6Jasmine - 无法读取未定义的属性“控件” - Angular 6
【发布时间】:2018-11-02 16:54:46
【问题描述】:

我是 Angular 的初学者,我正在 Jasmine 中为我的 Angular6 EmployeeComponent 编写一个测试。我有一个错误

无法读取未定义的属性“控件”

我认为我没有在employee.component.spec.ts 文件中正确设置所有依赖项。我研究了几天,但没有结果。任何帮助将不胜感激。

这是我的代码中的基本流程。员工组件包含一个注册表单。当用户点击Submit时,员工服务会将数据插入/更新到firebase。

下面是employee.component.html

 <div class="row">
  <div class="col-md-5">
    <form [formGroup]="this.employeeService.form" (ngSubmit)="onSubmit()">
      <input type="hidden" formControlName="$key">
      <div class="form-group" *ngIf="page1">
        <label>Full Name</label>
        <input formControlName="fullName" class="form-control" [ngClass]="{'is-invalid': submitted && formControls.fullName.errors}">
        <div class="invalid-feedback" *ngIf="submitted && formControls.fullName.errors">
          This field is required.
        </div>
      </div>

      <div class="form-group" *ngIf="page1">
        <label>Email</label>
        <input formControlName="email" class="form-control" [ngClass]="{'is-invalid': submitted && formControls.email.errors}">
        <div class="invalid-feedback" *ngIf="submitted && formControls.email.errors">
            Invalid Email Address.
          </div>
      </div>

      <div class="form-group" *ngIf="page1">
        <label>Mobile</label>
        <input formControlName="mobile" class="form-control" [ngClass]="{'is-invalid': submitted && formControls.mobile.errors}">
        <div class="invalid-feedback" *ngIf="submitted && formControls.mobile.errors">
          <label *ngIf="formControls.mobile.errors.required">This field is required.</label>
          <label *ngIf="formControls.mobile.errors.minLength">At least 8 characters</label>
        </div>
      </div>

      <div style="text-align: right" *ngIf="page1">
        <button class="btn btn-primary" (click)="part1Next()">Next</button>
      </div>

      <div class="form-group" *ngIf="page2">
          <label>School</label>
          <input formControlName="school" class="form-control">
      </div>

      <!-- <div class="form-group" *ngIf="page2">
        <label>Degree</label>
        <input formControlName="degree" class="form-control">
      </div> -->

      <div class="form-group" *ngIf="page2">
        <label>Degree</label>
        <select class="form-control" formControlName="degree">
          <option value="bachelor">Bachelor</option>
          <option value="master">Master</option>
        </select>
      </div>

      <div class="form-group" *ngIf="page2">
          <label>Location</label>
          <input formControlName="location" class="form-control">
      </div>

      <div class="row">
          <div class="col-md-6" style="text-align: center" *ngIf="page2">
              <button class="btn btn-primary" (click)="part2Back()">Back</button>
          </div>

          <div class="col-md-6" style="text-align: center" *ngIf="page2">
              <button class="btn btn-primary" (click)="part2Next()">Next</button>
          </div>
      </div>

      <div *ngIf="page3">
          <div>
            <h2>Please Check all of the information below</h2>
            <p>Full Name: {{formControls.fullName.value}}</p>
            <p>Email: {{formControls.email.value}}</p>
            <p>Mobile: {{formControls.mobile.value}}</p>
            <p>School: {{formControls.school.value}}</p>
            <p>Degree: {{formControls.degree.value}}</p>
            <p>Location: {{formControls.location.value}}</p>
          </div>
          <div class="row">
              <div class="col-md-6" style="text-align: center">
                  <button class="btn btn-primary" (click)="part3Back()">Back</button>
              </div>

              <div class="col-md-6" style="text-align: center" class="form-group">
                  <input type="submit" class = "btn btn-primary" value="Submit">
              </div>
          </div>
      </div>

    </form>
    <div class="alert alert-info" *ngIf="showSuccessMessage">
      Submitted Successfully
    </div>

  </div>

  <div class="col-md-7">
    <!-- <app-employee-list></app-employee-list> -->
  </div>

</div>

下面是employee.component.ts

import { Component, OnInit } from '@angular/core';

import { EmployeeService } from '../shared/employee.service';

@Component({
  selector: 'app-employee',
  templateUrl: './employee.component.html',
  styleUrls: ['./employee.component.scss']
})
export class EmployeeComponent implements OnInit {

  constructor(private employeeService: EmployeeService) { }

  page1: boolean = true;
  page2: boolean = false;
  page3: boolean = false;

  part1Next() {
    this.page1 = false;
    this.page2 = true;
  }

  part2Back() {
    this.page1 = true;
    this.page2 = false;
  }

  part2Next() {
    this.page2 = false;
    this.page3 = true;
  }

  part3Back() {
    this.page2 = true;
    this.page3 = false;
  }

  submitted: boolean;
  showSuccessMessage: boolean;
  formControls = this.employeeService.form.controls;

  ngOnInit() {
  }

  onSubmit() {
    this.submitted = true;
    if (this.employeeService.form.valid) {
      if (this.employeeService.form.get('$key').value == null) {
        // insert
        this.employeeService.insertEmployee(this.employeeService.form.value);
      } else {
        // update
        this.employeeService.updateEmployee(this.employeeService.form.value);
      }
      this.showSuccessMessage = true;
      setTimeout(() => this.showSuccessMessage = false, 2000);
      this.submitted = false;
      this.employeeService.form.reset();
    }
    this.page1 = true;
    this.page2 = false;
    this.page3 = false;
  }
}

下面是employee.component.spec.ts

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { EmployeeComponent } from './employee.component';
import { FormsModule } from '@angular/forms';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { EmployeeService } from '../shared/employee.service';

class MockEmpService { 
  employeeList: AngularFireList<any>;

  form = new FormGroup({
    $key: new FormControl(null),
    fullName: new FormControl('', Validators.required),
    email: new FormControl('', Validators.email),
    mobile: new FormControl('', [Validators.required, Validators.minLength(8)]),
    school: new FormControl(''),
    degree: new FormControl(''),
    location: new FormControl('')
  });

  insertEmployee() {
    return true;
  }

  updateEmployee() {
    return true;
  }
}

fdescribe('EmployeeComponent', () => {
  let component: EmployeeComponent;
  let fixture: ComponentFixture<EmployeeComponent>;
  let empService: MockEmpService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ EmployeeComponent ],
      imports: [ ReactiveFormsModule, FormsModule ],
      providers: [ 
        {provide: EmployeeService, useValue: MockEmpService},  
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(EmployeeComponent);
    component = fixture.componentInstance;
    empService = TestBed.get(MockEmpService);
    fixture.detectChanges();
  });

  // first test
  // when onSubmit() from component is called, insert() or update() from service is called
  it('calling insert or update from service when onSubmit is called', () => {
    spyOn(component, 'onSubmit');
    expect(empService.insertEmployee).toHaveBeenCalled();
  });

  // Test created automatically
  // it('should create', () => {
  //   expect(component).toBeTruthy();
  // });
});

下面是employee.service.ts

import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {
  employeeList: AngularFireList<any>;

  constructor(private firebase: AngularFireDatabase) { }
  form = new FormGroup({
    $key: new FormControl(null),
    fullName: new FormControl('', Validators.required),
    email: new FormControl('', Validators.email),
    mobile: new FormControl('', [Validators.required, Validators.minLength(8)]),
    school: new FormControl(''),
    degree: new FormControl(''),
    location: new FormControl('')
  });

  getCustomers() {
    this.employeeList = this.firebase.list('employees');
    console.log(this.employeeList);
    return this.employeeList.snapshotChanges();
  }

  insertEmployee(employee) {
    this.employeeList.push({
      fullName: employee.fullName,
      email: employee.email,
      mobile: employee.mobile,
      school: employee.school,
      degree: employee.degree,
      location: employee.location
    });
  }

  updateEmployee(employee) {
    this.employeeList.update(employee.$key,
      {
        fullName: employee.fullName,
        email: employee.email,
        mobile: employee.mobile,
        school: employee.school,
        degree: employee.degree,
        location: employee.location
      });
  }

  populateForm(employee) {
    this.form.setValue(employee);
  }

  deleteEmployee($key: string) {
    this.employeeList.remove($key);
  }
}

【问题讨论】:

  • 你能提供你的employee.component.spec.ts文件吗?
  • 你好丹尼尔。谢谢你提醒我。

标签: angular testing jasmine karma-runner angular-reactive-forms


【解决方案1】:

Anh,欢迎来到 StackOverflow。 :) 首先,这是您第一次写的非常详尽的问题。干得好!

因为它非常彻底,所以我能够组织一次堆栈闪电战来测试您遇到的问题。你可以在这里找到它:Jasmine - Cannot read property 'controls' of undefined - Angular 6

正如您在 stackblitz 中看到的,您的测试现在通过了。以下是我为完成这项工作所做的工作:

  • 将 EmployeeService 的 provider 行更改为 useClass 而不是 useValue - 您正在用一个类替换另一个类。
  • 在“let”声明和“TestBed.get(EmployeeService)”调用中将empService 的类型更改为EmployeeService。原因是您使用不同的类模拟 EmployeeService,但就组件而言,它仍然使用原始名称。
  • 注释掉了这一行:spyOn(component, 'onSubmit');,因为这会破坏您尝试测试的功能。
  • 创建了 insertSpy 来监视 empService.insertEmployee() 函数
  • 添加了 let formValidSpy = spyOnProperty(empService.form, 'valid', 'get').and.returnValue(true); 行,因为您需要模拟设置为有效或不设置有效的表单来控制 onSubmit() 的哪些部分被执行。
  • 最后用component.onSubmit();实际调用函数来测试它

有关所有详细信息,请参阅 stackblitz。我希望这会有所帮助。

【讨论】:

  • 非常感谢 dmcgrandle。我真的很感激。
  • 你好@dmcgrandle。在 EmployeeComponent 的 onSubmit() 函数中,EmployeeService 将根据 this.employeeService.form.get('$key').value 的返回值调用 insertEmployee() 或 updateEmployee()。我试过这种方式来调用 updateEmployee 函数,但似乎不正确:it('calling update employee when onSubmit is called', () =&gt; { let spy = spyOn(empService.form, 'get').and.returnValue(null); component.onSubmit(); expect(empService.updateEmployee).toHaveBeenCalled(); }); 而且它不起作用。对此有什么想法吗?谢谢。
  • 嗨@Anh Ngo。在阅读 onSubmit() 函数中的逻辑时,调用 updateEmployee() 似乎需要两件事。 1.this.employeeService.form.valid必须为真。 2.this.employeeService.form.get('$key').value 不能为空。一旦这两件事都成立,那么 updateEmployee() 将被调用,因此您应该设置一个间谍来 spyOn 该函数并确保它被调用。测试 updateEmployee() 调用的规范必须在调用 component.onSubmit() 之前设置所有三件事。 stackblitz 现在更新了一种方法来实现所有这 3 件事。
  • 非常感谢@dmcgrandle。现在很有意义。
  • 我肯定会赞成@dmcgrandle 这两个答案。现在,我的声望还不到 15,但我会在达到 15 时立即这样做。:))。
猜你喜欢
  • 1970-01-01
  • 2021-02-27
  • 2018-02-02
  • 2019-03-17
  • 1970-01-01
  • 1970-01-01
  • 2018-12-09
  • 2018-12-13
  • 2019-03-25
相关资源
最近更新 更多