首页 文章

Angular2 NgModel在Jasmine测试中没有获得 Value

提问于
浏览
6

我在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>

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

2 回答

  • 2

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

    那是因为当你在 beforeEach 中触发 fixture.detectChanges(); 时,那些测试中的所有代码都在 fakeAsync 区域内执行 . 所以 fakeAsync 区域没有't know about async operation outside its scope. When you'重新调用 detectChanges 第一次 ngModel 被初始化

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

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

    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
    

    setUpControl 内,您可以看到 input 事件将调用的函数

    dir.valueAccessor.registerOnChange(function (newValue) {
      dir.viewToModelUpdate(newValue);
      control.markAsDirty();
      control.setValue(newValue, { emitModelToViewChange: false });
    });
    

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

    Plunker Example

    但是这个解决方案似乎非常复杂,因为你需要重写你的代码以在每个 it 语句中移动 fixture.detectChanges (并且 skillCountsubmitButton 等也存在问题)

    2)正如Dinistro所说 asyncwhenStable 也应该帮助你:

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

    Plunker Example

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

    3)只需将 async 添加到beforeEach函数中

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

    Plunker Example

  • 12

    我认为 whenStable 应该在你的情况下做到这一点:

    it('submits the value',() => {
        fixture.whenStable().then(() => {
            // ngModel should be available here 
        })
    });
    

    更多信息:https://angular.io/docs/ts/latest/guide/testing.html#!#when-stable

    EDIT: 如果直接操作DOM元素的值, ValueAccessor 似乎没有注意到更改 . 但它应该注意到,如果你直接用 ValueAccessor 写入值:

    const skillCount= fixture.debugElement.query(By.directive(NgModel));
    const ngModel= skillCount.injector.get(NgModel);
    
    ngModel.valueAccessor.writeValue('10')
    

相关问题