【问题标题】:Updating input html field from within an Angular 2 test从 Angular 2 测试中更新输入 html 字段
【发布时间】:2017-01-27 16:56:54
【问题描述】:

我想在 Angular 2 单元测试中更改输入字段的值。

<input type="text" class="form-control" [(ngModel)]="abc.value" />

我不能只更改ngModel,因为abc 对象是私有的:

 private abc: Abc = new Abc();

在 Angular 2 测试中,我可以模拟用户在输入字段中的输入,以便 ngModel 将更新为用户在单元测试中输入的内容吗?

我可以毫无问题地抓取输入字段的DebugElementnativeElement。 (只是在输入字段的nativeElement 上设置value 属性似乎不起作用,因为它不会用我为值设置的值更新ngModel

也许可以调用inputDebugEl.triggerEventHandler,但我不确定要给它什么参数,所以它会模拟用户输入了特定的输入字符串。

【问题讨论】:

    标签: unit-testing testing angular jasmine


    【解决方案1】:

    接受的解决方案在 Angular 2.4 中对我来说不太适用。即使在调用了 detectChanges() 之后,我设置的值也没有出现在(测试)UI 中。

    我让它工作的方式是按如下方式设置我的测试:

    describe('TemplateComponent', function () {
      let comp: TemplateComponent;
      let fixture: ComponentFixture<TemplateComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [ FormsModule ],
          declarations: [ TemplateComponent ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(TemplateComponent);
        comp = fixture.componentInstance;
      });
    
      it('should allow us to set a bound input field', fakeAsync(() => {
        setInputValue('#test2', 'Tommy');
    
        expect(comp.personName).toEqual('Tommy');
      }));
    
      // must be called from within fakeAsync due to use of tick()
      function setInputValue(selector: string, value: string) {
        fixture.detectChanges();
        tick();
    
        let input = fixture.debugElement.query(By.css(selector)).nativeElement;
        input.value = value;
        input.dispatchEvent(new Event('input'));
        tick();
      }
    });
    

    在此示例中,我的 TemplateComponent 组件有一个名为 personName 的属性,这是我在模板中绑定的模型属性:

    &lt;input id="test2" type="text" [(ngModel)]="personName" /&gt;

    【讨论】:

    • 这个答案结束了我 3 小时的痛苦
    • 为此,在调用setInput 之后,我必须使用fixture.whenStable( () =&gt; { expect(...) }); 块。
    • 请注意@redOctober13,您应该使用对flush()(在Angular 4.2+ 中)或tick()(在早期版本中)的调用,而不是使用fixture.whenStable().whenStable() 旨在与async() 一起使用,而不是fakeAsync()。当我们有一次违反此规则时,我的团队花了数小时追查奇怪的测试行为(即测试仅在某些时候通过)。
    • 谢谢@paul,很高兴知道。您是否在任何地方找到了该文件?
    • @redOctober13 我还没有看到它明确记录。但是经验告诉我的团队要避免它。 fakeAsync afaik 的全部意义在于启用类似同步的测试以避免需要 whenStable。 Angular.io 站点的测试页面很好地总结了预期用途,它们从不结合 fakeAsync 和 whenStable。也许这已经足够证明了......
    【解决方案2】:

    我也很难得到 jonrsharpe 的回答来使用 Angular 2.4。我发现对fixture.detectChanges()fixture.whenStable() 的调用导致表单组件重置。测试开始时,似乎某些初始化函数仍在等待中。我通过在每次测试之前添加对这些方法的额外调用来解决这个问题。这是我的代码的 sn-p:

    beforeEach(() => {
        TestBed.configureTestingModule({
            // ...etc...
        });
        fixture = TestBed.createComponent(LoginComponent);
        comp = fixture.componentInstance;
        usernameBox = fixture.debugElement.query(By.css('input[name="username"]'));
        passwordBox = fixture.debugElement.query(By.css('input[type="password"]'));
        loginButton = fixture.debugElement.query(By.css('.btn-primary'));
        formElement = fixture.debugElement.query(By.css('form'));
    });
    
    beforeEach(async(() => {
        // The magic sauce!!
        // Because this is in an async wrapper it will automatically wait
        // for the call to whenStable() to complete
        fixture.detectChanges();
        fixture.whenStable();
    }));
    
    function sendInput(inputElement: any, text: string) {
        inputElement.value = text;
        inputElement.dispatchEvent(new Event('input'));
        fixture.detectChanges();
        return fixture.whenStable();
    }
    
    it('should log in correctly', async(() => {
    
        sendInput(usernameBox.nativeElement, 'User1')
        .then(() => {
            return sendInput(passwordBox.nativeElement, 'Password1')
        }).then(() => {
            formElement.triggerEventHandler('submit', null);
            fixture.detectChanges();
    
            let spinner = fixture.debugElement.query(By.css('img'));
            expect(Helper.isHidden(spinner)).toBeFalsy('Spinner should be visible');
    
            // ...etc...
        });
    }));
    

    【讨论】:

    • 即使我不使用whenStablemagic sauce 提示帮助我摆脱了痛苦:我的输入字段在测试期间神秘地保持空白。将async 添加到beforeEach(async(() =&gt; { ... component = fixture.componentInstance; fixture.detectChanges(); })); 使该字段最终被填充。
    【解决方案3】:

    您是对的,您不能只设置输入,还需要调度 'input' 事件。这是我今晚早些时候写的一个输入文本的函数:

    function sendInput(text: string) {
      inputElement.value = text;
      inputElement.dispatchEvent(new Event('input'));
      fixture.detectChanges();
      return fixture.whenStable();
    }
    

    这里fixtureComponentFixtureinputElement 是来自灯具nativeElement 的相关HTTPInputElement。这会返回一个承诺,因此您可能必须解决它sendInput('whatever').then(...)

    在上下文中:https://github.com/textbook/known-for-web/blob/52c8aec4c2699c2f146a33c07786e1e32891c8b6/src/app/actor/actor.component.spec.ts#L134


    更新

    我们在 Angular 2.1 中让它工作时遇到了一些问题,它不喜欢创建 new Event(...),所以我们这样做了:

    import { dispatchEvent } from '@angular/platform-browser/testing/browser-util';
    
    ...
    
    function sendInput(text: string) {
      inputElement.value = text;
      dispatchEvent(fixture.nativeElement, 'input');
      fixture.detectChanges();
      return fixture.whenStable();
    }
    

    【讨论】:

    • 非常感谢乔恩。这很好用,并且 ngModel 会更新为所需的文本。你的回答确实为我节省了很多时间。
    • 您在实现此功能时是否发现了其他问题?我试图让这个解决方案发挥作用,但到目前为止还无法让模型更新。我在 Angular 2.1.2 上,所以不确定自从这篇文章以来是否有什么东西坏了/改变了。
    • @NathanG 我不记得了。尝试更多或更少的更改检测并等待稳定性可能会有所帮助。
    • @NathanG 他们确实改变了一些东西。我也无能为力。我假设测试区不再对事件做出反应,这是我能想到的唯一解释。还是没有解决办法
    • 我决定通过恢复我的项目以使用 @jonrsharpe 用于他的示例的 Angular 版本来测试这个理论,但这并没有改变结果。所以也许我做错了什么。我将为我的代码提供一些图像,希望有人发现明显不正确的东西:
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 2019-06-24
    • 2012-09-23
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多