Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I trigger a ngModel model update in an Angular 2 unit test?

I am attempting to test a component containing a text input element. I want to verify that the state of the component changes as expected when the value of the text input changes. The text input, of course, utilizes the ngModel directive (two-way binding).

Although the component works fine at runtime, I'm having trouble creating a valid, passing test. I have posted what I think should work below, and the test result following that.

What am I doing wrong?

TEST:

import {Component} from 'angular2/core';
import {describe, it, injectAsync, TestComponentBuilder} from 'angular2/testing';
import {FORM_DIRECTIVES} from 'angular2/common';
import {By} from 'angular2/platform/common_dom';

class TestComponent {
    static get annotations() {
        return [
            new Component({
                template: '<input type="text" [(ngModel)]="name" /><p>Hello {{name}}</p>',
                directives: [FORM_DIRECTIVES]
            })
        ]
    }
}

describe('NgModel', () => {
    it('should update the model', injectAsync([TestComponentBuilder], tcb => {
        return tcb
            .createAsync(TestComponent)
            .then(fixture => {
                fixture.nativeElement.querySelector('input').value = 'John';
                const inputElement = fixture.debugElement.query(By.css('input'));
                inputElement.triggerEventHandler('input', { target: inputElement.nativeElement });
                fixture.detectChanges();
                expect(fixture.componentInstance.name).toEqual('John');
            });
    }));
});

OUTPUT:

Chrome 45.0.2454 (Mac OS X 10.10.5) NgModel should update the model FAILED
    Expected undefined to equal 'John'.
        at /Users/nsaunders/Projects/ng2-esnext-seed/src/test/ngmodel.spec.js!transpiled:41:52
        at ZoneDelegate.invoke (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:322:29)
        at Zone.run (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:218:44)
        at /Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:567:58
        at ZoneDelegate.invokeTask (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:355:38)
        at Zone.runTask (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:254:48)
        at drainMicroTaskQueue (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:473:36)
        at XMLHttpRequest.ZoneTask.invoke (/Users/nsaunders/Projects/ng2-esnext-seed/node_modules/angular2/bundles/angular2-polyfills.js:425:22)
like image 855
Nick Saunders Avatar asked Mar 21 '16 23:03

Nick Saunders


People also ask

How do I update my ngModel value?

If we use two way binding syntax for ngModel the value will be updated. So the default (ngModelChange) function will update the value of ngModel property. i.e., user.Name . And the second (ngModelChange) will be triggered printing the user name value in the console.

How does ngModel work in Angular 2?

The Angular uses the ngModel directive to achieve the two-way binding on HTML Form elements. It binds to a form element like input , select , selectarea . etc. Internally It uses the ngModel in property, binding to bind to the value property and ngModelChange which binds to the input event.

What does [( ngModel )] mean?

The ngModel directive is a directive that is used to bind the values of the HTML controls (input, select, and textarea) or any custom form controls, and stores the required user value in a variable and we can use that variable whenever we require that value.

Which module do you need to import to use [( ngModel )]?

The ngmodel directive is not part of the Angular Core library. It is part of the FormsModule library. You need to import the FormsModule package into your Angular module.


2 Answers

Here's a way to do it:

import {Component} from 'angular2/core';
import {describe, it, inject, fakeAsync, tick, TestComponentBuilder} from 'angular2/testing';
import {FORM_DIRECTIVES} from 'angular2/common';

class TestComponent {
    static get annotations() {
        return [
            new Component({
                template: '<input type="text" [(ngModel)]="name" /><p>Hello {{name}}</p>',
                directives: [FORM_DIRECTIVES]
            })
        ]
    }
}

describe('NgModel', () => {
    it('should update the model', inject([TestComponentBuilder], fakeAsync(tcb => {
        let fixture = null;
        tcb.createAsync(TestComponent).then(f => fixture = f);
        tick();
    
        fixture.detectChanges();
        let input = fixture.nativeElement.querySelector('input');
        input.value = 'John';
        let evt = document.createEvent('Event');
        evt.initEvent('input', true, false);
        input.dispatchEvent(evt);
        tick(50);
    
        expect(fixture.componentInstance.name).toEqual('John');
    })));
});
like image 108
Nick Saunders Avatar answered Sep 21 '22 16:09

Nick Saunders


NgModel listens to the input event to get notified about changes:

dispatchEvent(inputElement, "input");
tick();

For other input elements other events might be used (checkbox, radio, option,...).

like image 41
Günter Zöchbauer Avatar answered Sep 19 '22 16:09

Günter Zöchbauer