5

I've read the documentation for testing two-way binding in Angular2. All examples are simple, too simple, actually.

It seems like they only test the out-binding...

I'll post some code to illustrate:

import { Component, Input } from "@angular/core";
import { ComponentFixture, async, TestBed, tick, fakeAsync } from 
"@angular/core/testing";
import { FormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";

@Component({
    selector: 'test',
    template: `
        <input type="text" [(ngModel)]="myValue" />
        <div>{{ myValue }}</div>
    `
})
class TestComponent {
    @Input() myValue: string
}

describe('Example', () => {
    let component: TestComponent
    let fixture: ComponentFixture<TestComponent>

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent],
            providers: [],
            imports: [FormsModule]

        })
            .compileComponents()
    }))

    beforeEach(() => {
        fixture = TestBed.createComponent(TestComponent)
        component = fixture.componentInstance

        fixture.detectChanges()
    })

    it('should test two-way binding by setting the component member', 
        fakeAsync(() => {

            const testValue = 'Component member test'

            component.myValue = testValue // Should be the correct way to 
            test ngModel

            tick();
            fixture.detectChanges();

            // Assertion error: Expected '' to equal 'Component member test'
            // Why wasn't the value set in the textbox?
            expect(fixture.debugElement.query(By.css('input'))
.nativeElement.value).toEqual(testValue)

            //Yeah, the bananas are working. We have out-binding
            expect(fixture.debugElement
                .query(By.css('div'))
                .nativeElement
                .textContent
            ).toEqual(testValue);
    }))

    it('should test two-way binding by setting value directly on the native 
element. But that just tests the out-binding', fakeAsync(() => {
            const testValue = 'NativeElement test'

            let element = 
fixture.debugElement.query(By.css('input')).nativeElement;
            element.value = testValue // this tests only the out-binding
            element.dispatchEvent(new Event('input'));

            tick();
            fixture.detectChanges();

            //of course this is Ok, we just set it directly
            expect(fixture.debugElement.query(By.css('input'))
.nativeElement.value).toEqual(testValue)

            //Yeah, the bananas are working. We have out-binding
            expect(fixture.debugElement
                .query(By.css('div'))
                .nativeElement
                .textContent
            ).toEqual(testValue);
    }))
})

So is there a way actually test the component by setting myValue? I think that I am missing something...

2 Answers 2

9

Try swapping two lines:

tick();
fixture.detectChanges();

so it should be

fixture.detectChanges();
tick();

It means firstly you need to set value and then wait while angular is updating control value because it will happen inside Promise.then

https://github.com/angular/angular/blob/4.1.0-rc.0/packages/forms/src/directives/ng_model.ts#L213-L214

Plunker Example

Sign up to request clarification or add additional context in comments.

3 Comments

Ha! Okay, now the example works. But my real world code doesn't. I thing it's because detectChanges doesn't detect changes for sub-components.
It fails no matter my ChangeDetectionStrategy... I'm accepting your answer because it fixed the example :-)
After 5h hours of trying to see why my input does not have a correct value using fakeAsync (with fixture.whenStable works) it all comes down to swapping these two lines.....programming is fun!
0

you have to add name attribute in your html input tag

<input type="text" [(ngModel)]="myValue" name="myValue"/>

1 Comment

Thanks, but that doesn't fix the test. If you add a form-tag however, name is required.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.