Angular2 NgModel not getting value in Jasmine test

后端 未结 2 1427
天命终不由人
天命终不由人 2021-02-05 17:54

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 everyth

相关标签:
2条回答
  • 2021-02-05 18:37

    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/%40angular/forms/test/template_integration_spec.ts#L1043

    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
    

    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) 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

    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) 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) Just add async to your beforeEach function

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

    Plunker Example

    0 讨论(0)
  • 2021-02-05 18:40

    I think whenStable should do the trick in your case:

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

    More information here: https://angular.io/docs/ts/latest/guide/testing.html#!#when-stable

    EDIT: The ValueAccessor seems not to notice the change if you directly manipulate the value of the DOM-element. But it should notice, if you write the value directly with the ValueAccessor:

    const skillCount= fixture.debugElement.query(By.directive(NgModel));
    const ngModel= skillCount.injector.get(NgModel);
    
    ngModel.valueAccessor.writeValue('10')
    
    0 讨论(0)
提交回复
热议问题