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

Posted

技术标签:

【中文标题】Angular2 NgModel 在 Jasmine 测试中没有获得价值【英文标题】:Angular2 NgModel not getting value in Jasmine test 【发布时间】:2017-07-14 14:12:32 【问题描述】:

我在 Angular 2 中使用模板驱动的表单,我正在尝试先测试开发它们。我已经搜索了这个网站和互联网的其他部分,并且我基本上已经尝试了我能找到的所有东西(主要是一堆 tick() 语句和在 fakeAsync 中随处可见的 detectChanges()),以将 NgModel 附加到我的输入以获取值,以便可以将其传递给我的 onSubmit 函数。输入元素的值设置正确,但 NgModel 永远不会更新,这意味着 onSubmit 函数没有从 NgModel 中获取正确的值。

这是模板:

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

这是组件:

从'@angular/core'导入组件,OnInit; 从“../model/skill-service”导入SkillService; 从“@angular/forms”导入 NgModel; @零件( 选择器:'app-startworkout', templateUrl: './startworkout.component.html', styleUrls: ['./startworkout.component.css'] ) 导出类 StartworkoutComponent 实现 OnInit 公共技能计数:字符串; 构造函数(公共技能服务:技能服务) showWorkout(值:NgModel):无效 console.log('断点', value.value); ngOnInit()

这是规格:

/* tslint:disable:no-unused-variable */ 从'@angular/core/testing'导入async,ComponentFixture,TestBed,fakeAsync,tick; 从 '@angular/platform-b​​rowser' 导入 By, BrowserModule; 从“@angular/core”导入 DebugElement ; 从'./startworkout.component'导入 StartworkoutComponent ; 从“../model/skill-service”导入SkillService; 从“../core/store”导入Store; 从“../model/sport-service”导入 SportService; 从“@angular/forms”导入 FormsModule; 从“@angular/platform-b​​rowser/testing/browser_util”导入 dispatchEvent; 描述('StartworkoutComponent', () => 让组件:StartworkoutComponent; 让夹具:ComponentFixture; 让元素:调试元素; 让技能服务:技能服务; beforeEach(异步(() => var storeSpy:any = jasmine.createSpyObj('store', ['getValue', 'storeValue', 'removeValue']); var stubSkillService:SkillService = new SkillService(storeSpy); TestBed.configureTestingModule( 声明:[ StartworkoutComponent ], 提供者:[provide:Store, useValue:storeSpy, SportService, SkillService], 进口:[BrowserModule,FormsModule] ) .compileComponents(); )); beforeEach(() => 夹具 = TestBed.createComponent(StartworkoutComponent); 组件=fixture.componentInstance; 元素=fixture.debugElement; 夹具.detectChanges(); ); it('应该创建', () => 期望(组件).toBeTruthy(); ); describe('没有锻炼', () => 让 createWorkout:DebugElement; 让技能计数:HTMLInputElement; 让 submitButton:HTMLButtonElement; beforeEach(() => createWorkout = element.query(By.css('#createWorkout')); SkillCount = element.query(By.css('#skillCount')).nativeElement; submitButton = element.query(By.css('#buildWorkout')).nativeElement; ); it('有 createWorkout 表格', () => 期望(createWorkout).toBeTruthy(); 期望(skillCount).toBeTruthy(); ); it('提交值', fakeAsync(() => spyOn(component, 'showWorkout').and.callThrough(); 打钩(); 技能计数.value = '10'; dispatchEvent(skillCount, '输入'); 夹具.detectChanges(); 勾号(50); submitButton.click(); 夹具.detectChanges(); 勾号(50); 期望(component.showWorkout).toHaveBeenCalledWith('10'); )); ); );

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

编辑:

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

<p>cwf.value | json</p>

进入表格,它只显示。我相信它应该显示一个代表输入的成员属性。如果我在该字段中输入,该值不会改变。如果我尝试绑定到 SkillCountFld,也会发生类似的事情。所以我认为基本表单设置在某种程度上是不正确的,并且在输入正确连接到 SkillCountFld 控制器之前,测试永远不会起作用。我只是看不出我错过了什么。

【问题讨论】:

如果你在showWorkout函数中传递ngModel,你为什么期望toHaveBeenCalledWith(10) @Amy Blankenship,与NgModel 有一个非常相似的问题,而在反应表单模块的情况下,绑定没有问题。 Figured out how to get around it which can probably be of any help to you. 【参考方案1】:

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

即可设置

这是因为当您在beforeEach 内触发fixture.detectChanges(); 时,这些测试中的所有代码都在fakeAsync 区域内执行。所以fakeAsync zone 不知道其范围之外的异步操作。当您第一次调用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.detectChangesskillCountsubmitButton 等也存在问题)

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

【讨论】:

将异步添加到 beforeEach 修复了它,谢谢!我不知道为什么在 Debug 中的浏览器中可见的表单会受到影响,但显然是这样。 将异步添加到 beforeEach 修复了我的情况,这与 Amy 的情况相同【参考方案2】:

我认为 whenStable 应该可以解决您的问题:

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

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

编辑: 如果您直接操作 DOM 元素的值,ValueAccessor 似乎不会注意到变化。但是应该注意,如果你直接用ValueAccessor写值:

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

ngModel.valueAccessor.writeValue('10')

【讨论】:

我实际上不认为这是问题所在。当我将skillCountFld.value 放在屏幕上的 p 中时(当您单击 Karma 中的“调试”按钮时可见),即使您在字段中手动输入值,它也会显示为 null。另外,有很多测试at the Angular site 无需等待whenStable 即可成功设置。我倾向于认为我没有正确设置我的输入,但我没有看到有什么问题。 @AmyBlankenship 我认为如果您像您一样直接更改输入元素的值,valueAccessor 不会注意到它,因此不会触发。我用一种肯定会触发valueAccessor 的方法编辑了我的答案 这无法解释为什么当您在控件中键入时 ngModel 的值不会出现在浏览器的屏幕上,或者为什么当您绑定到没有内容的cwf.value | json 时,甚至没有代表的成员技能计数。我认为在设置正确之前,对测试的任何调整都是红鲱鱼。

以上是关于Angular2 NgModel 在 Jasmine 测试中没有获得价值的主要内容,如果未能解决你的问题,请参考以下文章

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

ngModel 值未显示在 angular2 的控件中

ngModel innerHTML 在 p 标签中有效,但在输入标签 angular2 中无效

Angular2/4 mat-select 多个 ngModel

在 Angular2 ngModel 值未在自定义指令的 onBlur 事件上更新

Angular2、inputmask ngModel 绑定