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-browser' 导入 By, BrowserModule; 从“@angular/core”导入 DebugElement ; 从'./startworkout.component'导入 StartworkoutComponent ; 从“../model/skill-service”导入SkillService; 从“../core/store”导入Store; 从“../model/sport-service”导入 SportService; 从“@angular/forms”导入 FormsModule; 从“@angular/platform-browser/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.detectChanges
从 beforeEach
移动到您的测试中,那么它应该可以工作:
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
(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');
)
));
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 innerHTML 在 p 标签中有效,但在输入标签 angular2 中无效
Angular2/4 mat-select 多个 ngModel