• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Angular2 NgModel在茉莉花测试未获得价值

用户头像
it1352
帮助1

问题说明

我正在Angular 2中使用模板驱动的表单,并且我尝试先进行测试来开发它们.我已经搜索了该站点以及互联网的其余部分,并且我已经尝试了所有我能找到的所有内容(主要是在fakeAsync中到处都是一堆tick()语句和detectChanges()),以便将NgModel附加到我的输入中以进行提取该值,以便可以将其传递给我的onSubmit函数.输入元素的值设置正确,但是NgModel永不更新,这意味着onSubmit函数无法从NgModel获取正确的值.

I am using template-driven forms in Angular 2, and I'm trying to develop them test-first. I've scoured this site and the rest of the internet and I've tried basically everything I can find (mainly bunches of tick() statements and detectChanges() everywhere in a fakeAsync) to get the NgModel attached to my input to pick up the value so it can be passed to my onSubmit function. The value of the input element sets properly, but the NgModel never updates, which then means the onSubmit function does not get the correct value from the NgModel.

这是模板:


<form   #cwf="ngForm" (ngSubmit)="showWorkout(skillCountFld)" novalidate>
  <input name="skillCount"     #skillCountFld="ngModel" ngModel />
  <button type="submit"  >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');
    }));
  });
});

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

I'm sure I'm missing something basic/simple, but I've spent the past day combing through everything I can find with no luck.

我认为也许人们专注于错误的事情.在这一点上,我非常确定我缺少有关ngForm和ngModel的工作原理的一些基本知识.当我添加

I think maybe people are focusing on the wrong thing. I'm pretty sure at this point that I'm missing something basic about how ngForm and ngModel work. When I add

&lt;p>{{cwf.value | json}}&lt;/p>

进入表单,它仅显示{}.我相信它应该显示代表输入的成员属性.如果我在该字段中键入,则该值不会更改.如果我尝试绑定到skillCountFld,也会发生类似的情况.因此,我认为基本表单设置在某种程度上是不正确的,并且在将输入正确连接到skillCountFld控制器之前,该测试永远无法进行.我只是看不到我所缺少的.

into the form, it just shows {}. I believe it should show a member property representing the input. If I type into the field, the value does not change. Similar things happen if I try to bind to skillCountFld. So I think the basic form setup is incorrect somehow, and the test is never going to work until the input is correctly wired to the skillCountFld controller. I just don't see what I'm missing.

正确答案

#1

在Angular站点上有很多成功的测试 无需等待whenStable > https: //github.com/angular/angular/blob/874243279d5fd2bef567a13e0cef8d0cdf68eec1/modules/@angular/forms/test/template_integration_spec.ts#L1043

There are a lot of tests at the Angular site that are successfully setting this without waiting for whenStable https://github.com/angular/angular/blob/874243279d5fd2bef567a13e0cef8d0cdf68eec1/modules/@angular/forms/test/template_integration_spec.ts#L1043

这是因为在beforeEach中触发fixture.detectChanges();时,这些测试中的所有代码都在fakeAsync区域内执行.因此,fakeAsync区域不了解其范围之外的异步操作.第一次调用detectChanges时,ngModel初始化

That's because all code in those tests is executed inside fakeAsync zone while you are firing fixture.detectChanges(); within beforeEach. So fakeAsync zone doesn't know about async operation outside its scope. When you're calling detectChanges first time ngModel is initialized

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

并为输入事件获取正确的回调

and gets right callback for input event

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事件调用的函数

inside setUpControl you can see function that will be called by input event

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

1)因此,如果将fixture.detectChangesbeforeEach移至测试,则它应该可以工作:

1) So if you move fixture.detectChanges from beforeEach to your test then it should work:

 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等也存在问题)

But this solution seems very complicated since you need to rewrite your code to move fixture.detectChanges in each of your it statements (and there is also a problem with skillCount, submitButton etc)

2)正如Dinistro所说,asyncwhenStable一起也应为您提供帮助:

2) As Dinistro said async together with whenStable should also help you:

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

但是等一下为什么我们必须更改代码?

but wait why do we have to change our code?

3)只需在您的beforeEach函数中添加async

3) Just add async to your beforeEach function

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

柱塞示例

Plunker Example

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /reply/detail/tanhchaege
系列文章
更多 icon
同类精品
更多 icon
继续加载