Angular2 NgModel在Jasmine测试中没有获得价值

Amy*_*hip 10 forms karma-jasmine angular

我在Angular 2中使用模板驱动的表单,我正在尝试先测试它们.我已经搜索过这个网站和互联网的其余部分了,我已经尝试了基本上我能找到的所有内容(主要是一串tick()语句和detectChanges()在fakeAsync中的任何地方)以获得附加到我的输入的NgModel来获取该值可以传递给我的onSubmit函数.input元素的值设置正确,但NgModel永远不会更新,这意味着onSubmit函数无法从NgModel获取正确的值.

这是模板:

<form id="createWorkout" #cwf="ngForm" (ngSubmit)="showWorkout(skillCountFld)" novalidate>
  <input name="skillCount" id="skillCount" class="form-control" #skillCountFld="ngModel" ngModel />
  <button type="submit" id="buildWorkout">Build a Workout</button>
</form>

注意:我知道发送ngSubmit的值会导致测试失败,但这意味着我可以在函数中设置断点并检查NgModel.

这是组件:

import { Component, OnInit } from '@angular/core';
import {SkillService} from "../model/skill-service";
import {NgModel} from "@angular/forms";

@Component({
  selector: 'app-startworkout',
  templateUrl: './startworkout.component.html',
  styleUrls: ['./startworkout.component.css']
})
export class StartworkoutComponent implements OnInit {
  public skillCount:String;

  constructor(public skillService:SkillService) { }

  showWorkout(value:NgModel):void {
    console.log('breakpoint', value.value);
  }

  ngOnInit() {
  }

}

这是规格:

/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {By, BrowserModule} from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { StartworkoutComponent } from './startworkout.component';
import {SkillService} from "../model/skill-service";
import {Store} from "../core/store";
import {SportService} from "../model/sport-service";
import {FormsModule} from "@angular/forms";
import {dispatchEvent} from "@angular/platform-browser/testing/browser_util";

describe('StartworkoutComponent', () => {
  let component: StartworkoutComponent;
  let fixture: ComponentFixture;
  let element:DebugElement;
  let skillService:SkillService;

  beforeEach(async(() => {
    var storeSpy:any = jasmine.createSpyObj('store', ['getValue', 'storeValue', 'removeValue']);
    var stubSkillService:SkillService = new SkillService(storeSpy);
    TestBed.configureTestingModule({
      declarations: [ StartworkoutComponent ],
      providers: [{provide:Store , useValue:storeSpy}, SportService, SkillService],
      imports: [BrowserModule, FormsModule]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(StartworkoutComponent);
    component = fixture.componentInstance;
    element = fixture.debugElement;
    fixture.detectChanges();

  });

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

  describe('without workout', () => {
    let createWorkout:DebugElement;
    let skillCount:HTMLInputElement;
    let submitButton:HTMLButtonElement;
    beforeEach(() => {
      createWorkout = element.query(By.css('#createWorkout'));
      skillCount = element.query(By.css('#skillCount')).nativeElement;
      submitButton = element.query(By.css('#buildWorkout')).nativeElement;
    });
    it('has createWorkout form', () => {
      expect(createWorkout).toBeTruthy();
      expect(skillCount).toBeTruthy();
    });
    it('submits the value', fakeAsync(() => {
      spyOn(component, 'showWorkout').and.callThrough();
      tick();
      skillCount.value = '10';
      dispatchEvent(skillCount, 'input');
      fixture.detectChanges();
      tick(50);
      submitButton.click();
      fixture.detectChanges();
      tick(50);
      expect(component.showWorkout).toHaveBeenCalledWith('10');
    }));
  });
});

我确定我错过了一些基本/简单的东西,但是我在过去的一天里一直在梳理我能找到的所有东西而没有运气.

编辑:

我想也许人们会关注错误的事情.我很确定在这一点上我遗漏了一些关于ngForm和ngModel如何工作的基本知识.当我添加

&lt;p>{{cwf.value | json}}&lt;/p>
Run Code Online (Sandbox Code Playgroud)

在表单中,它只显示{}.我相信它应该显示一个表示输入的成员属性.如果我输入字段,则值不会更改.如果我尝试绑定到skillCountFld,会发生类似的事情.所以我认为基本的表单设置是不正确的,并且在输入正确连接到skillCountFld控制器之前测试永远不会工作.我只是看不到我错过的东西.

yur*_*zui 17

Angular网站上有很多测试成功地设置了这个,而没有等待什么时候安装https://github.com/angular/angular/blob/874243279d5fd2bef567a13e0cef8d0cdf68eec1/modules/%40angular/forms/test/template_integration_spec.ts#L1043

那是因为这些测试中的所有代码都是在fakeAsync区域fixture.detectChanges();内执行的beforeEach.因此fakeAsynczone不知道其范围之外的异步操作.当你detectChanges第一次打电话时ngModel初始化

 NgModel.prototype.ngOnChanges = function (changes) {
            this._checkForErrors();
            if (!this._registered)
                this._setUpControl(); //<== here
Run Code Online (Sandbox Code Playgroud)

并获得输入事件的正确回调

NgForm.prototype.addControl = function (dir) {
  var _this = this;
  resolvedPromise.then(function () { // notice async operation
      var container = _this._findContainer(dir.path);
      dir._control = (container.registerControl(dir.name, dir.control));
      setUpControl(dir.control, dir); // <== here
Run Code Online (Sandbox Code Playgroud)

在里面setUpControl你可以看到将被input事件调用的函数

dir.valueAccessor.registerOnChange(function (newValue) {
  dir.viewToModelUpdate(newValue);
  control.markAsDirty();
  control.setValue(newValue, { emitModelToViewChange: false });
});
Run Code Online (Sandbox Code Playgroud)

1)因此,如果你fixture.detectChangesbeforeEach测试转到那么它应该工作:

 it('submits the value', fakeAsync(() => {
   spyOn(component, 'showWorkout').and.callThrough();
   fixture.detectChanges();

   skillCount = element.query(By.css('#skillCount')).nativeElement;
   submitButton = element.query(By.css('#buildWorkout')).nativeElement;

   tick();
   skillCount.value = '10';
   dispatchEvent(skillCount, 'input');
   fixture.detectChanges();

   submitButton.click();
   fixture.detectChanges();
   expect(component.showWorkout).toHaveBeenCalledWith('10');
}));
Run Code Online (Sandbox Code Playgroud)

Plunker示例

但是,这种解决方案似乎很复杂,因为你需要重写代码移动fixture.detectChanges在每个的it语句(和也有一个问题skillCount,submitButton等等)

2)正如Dinistro async一起说的那样whenStable也应该帮助你:

it('submits the value', async(() => {
  spyOn(component, 'showWorkout').and.callThrough();
  fixture.whenStable().then(() => {
    skillCount.value = '10';
    dispatchEvent(skillCount, 'input');
    fixture.detectChanges();

    submitButton.click();
    fixture.detectChanges();

    expect(component.showWorkout).toHaveBeenCalledWith('10');
  })
}));
Run Code Online (Sandbox Code Playgroud)

Plunker示例

但是等一下我们为什么要改变我们的代码呢?

3)只需添加async到您的beforeEach功能

beforeEach(async(() => {
  fixture = TestBed.createComponent(StartworkoutComponent);
  component = fixture.componentInstance;
  element = fixture.debugElement;
  fixture.detectChanges();
}));
Run Code Online (Sandbox Code Playgroud)

Plunker示例

  • 将 async 添加到 beforeEach 修复了它,谢谢!我不知道为什么在 Debug 的浏览器中可见的表单会受到影响,但显然是。 (2认同)