【问题标题】:How do I trigger a ngModel model update in an Angular 2 unit test?如何在 Angular 2 单元测试中触发 ngModel 模型更新?
【发布时间】:2016-07-08 17:14:49
【问题描述】:

我正在尝试测试包含文本输入元素的组件。我想验证当文本输入的值发生变化时组件的状态是否按预期变化。当然,文本输入使用 ngModel 指令(双向绑定)。

虽然该组件在运行时运行良好,但我无法创建有效的、通过的测试。我已经在下面发布了我认为应该起作用的内容,以及随后的测试结果。

我做错了什么?

测试:

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');
            });
    }));
});

输出:

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)

【问题讨论】:

  • 阅读source code怎么样?
  • 谢谢,我在搜索相关示例的来源时显然忽略了这一点。不幸的是,这个示例使用了看似私有的 API(来自 angular2/testing_internal 的 dispatchEvent),但它给了我至少尝试 fakeAsync 的想法。

标签: angular unit-testing


【解决方案1】:

这是一种方法:

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');
    })));
});

【讨论】:

【解决方案2】:

NgModel 侦听input 事件以获取有关更改的通知:

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

对于其他输入元素,可能会使用其他事件(复选框、单选、选项...)。

【讨论】:

  • 我在这里遇到错误:UNSPECIFIED_EVENT_TYPE_ERR: UNSPECIFIED_EVENT_TYPE_ERR: config/spec-bundle.js 中的 DOM 事件异常 0(第 49252 行)dispatchEvent@[native code]
  • 如果不看你的代码长什么样就很难判断。
  • 尼克桑德斯上面的回答似乎更准确
  • 真的很有帮助,让哥们@GünterZöchbauer 振作起来。
【解决方案3】:

我是这样处理的:

import {TestBed, ComponentFixture, async} from "@angular/core/testing";
import {dispatchEvent} from "@angular/platform-browser/testing/browser_util";
import {FormsModule} from "@angular/forms";
import {By} from "@angular/platform-browser";
import {DebugElement} from "@angular/core";

describe('my.component', () => {

    let comp: MyComp,
        fixture: ComponentFixture<MyComp>;

    beforeEach(async(() => {
            TestBed.configureTestingModule({
                imports: [FormsModule],
                declarations: [
                    MyComp
                ]
            }).compileComponents().then(() => {
                fixture = TestBed.createComponent(MyComp);
                comp = fixture.componentInstance;
                fixture.detectChanges();
            });
        })
    );

    it('should handle inputs', () => {
        const sendInputs = (inputs: Array<DebugElement>) => {
            inputs.forEach(input => {
                dispatchEvent(input.nativeElement, 'input');
            });
            fixture.detectChanges();
            return fixture.whenStable();
        };

        let inputs: Array<DebugElement> = fixture.debugElement.queryAll(By.css('input'));

        // set username input value
        inputs[0].nativeElement.value = "Username";

        // set email input value
        inputs[1].nativeElement.value = "Email";

        sendInputs(inputs).then(() => {
            // emit click event to submit form
            fixture.nativeElement.querySelectorAll('button')[0].click();

            expect(comp.username).toBe("Username");
            expect(comp.email).toBe("Email");
        });
    });
});

我的 html 看起来像这样:

<form (ngSubmit)="submit()">
    <input type="text" name="username" class="form-control" placeholder="username" required [(ngModel)]="username">
    <input type="email" name="email" class="form-control" placeholder="email" required [(ngModel)]="email">
    <button type="submit" class="btn btn-primary block full-width m-b">Submit</button>
</form>

我还建议检查对我有很大帮助的 angulars 源代码测试:Forms tests

【讨论】:

  • 您是否有一个未显示的名为 dispatchEvent 的自定义函数?因为我认为 dispatchEvent 应该在目标对象上调用。
猜你喜欢
  • 1970-01-01
  • 2017-03-07
  • 2015-05-04
  • 1970-01-01
  • 1970-01-01
  • 2017-05-16
  • 1970-01-01
  • 2017-08-28
  • 2017-12-15
相关资源
最近更新 更多