10

我有一个包含一个选择框,看起来像如何在angular2单元测试中更改选择框的值?

<select [(ngModel)]="envFilter" class="form-control" name="envSelector" (ngModelChange)="onChangeFilter($event)"> 
    <option *ngFor="let env of envs" [ngValue]="env">{{env}}</option> 
</select> 

我试图写的ngModelChange事件单元测试的Angular2组件。这是我最新的尝试失败

it("should filter and show correct items", async(() => { 
    fixture.detectChanges(); 
    fixture.whenStable().then(() => { 
     el = fixture.debugElement.query(By.name("envSelector")); 
     fixture.detectChanges(); 
     makeResponse([hist2, longhist]); 
     comp.envFilter = 'env3'; 
     el.triggerEventHandler('change', {}); 
     fixture.whenStable().then(() => { 
      fixture.detectChanges(); 
      expect(comp.displayedHistory).toEqual(longhist); 
     }); 
    }); 

我有困难的部分是,改变comp.envFilter = 'env3';不会触发改变方法的基本模型的价值。我加了el.triggerEventHandler('change', {});但是这个抛出了Failed: Uncaught (in promise): ReferenceError: By is not defined。我无法在文档中找到任何提示...任何想法?

回答

14

只要错误。看起来你只需要导入By。这不是全球性的。它应该从以下模块进口

import { By } from '@angular/platform-browser'; 

就测试部分而言,这是我所能弄清楚的。当您更改组件中的值时,需要触发更改检测以更新视图。你可以用fixture.detectChanges()来做到这一点。一旦完成,通常视图应该更新的值。

从测试类似于您的示例的东西,它似乎并非如此。看起来变化检测后仍然有一些异步任务正在进行。假设我们有以下几种:

const comp = fixture.componentInstance; 
const select = fixture.debugElement.query(By.css('select')); 

comp.selectedValue = 'a value; 
fixture.DetectChanges(); 
expect(select.nativeElement.value).toEqual('1: a value'); 

这似乎不起作用。看起来有一些异步正在导致值尚未设置。所以我们需要通过呼叫fixture.whenStable

comp.selectedValue = 'a value; 
fixture.DetectChanges(); 
fixture.whenStable().then(() => { 
    expect(select.nativeElement.value).toEqual('1: a value'); 
}); 

上面的工作。但现在我们需要触发更改事件,因为这不会自动发生。

fixture.whenStable().then(() => { 
    expect(select.nativeElement.value).toEqual('1: a value'); 

    dispatchEvent(select.nativeElement, 'change'); 
    fixture.detectChanges(); 
    fixture.whenStable().then(() => { 
    // component expectations here 
    }); 
}); 

现在我们有另一个来自事件的异步任务。所以我们需要重新稳定它

下面是我测试过的一个完整的测试。这是来自source code integration tests的示例的重构。他们使用fakeAsynctick,与使用asyncwhenStable类似。但与fakeAsync,你不能使用templateUrl,所以我虽然最好重构它使用async

此外源代码测试确实有双重单向测试,第一个测试模型来查看,然后查看模型。虽然看起来你的测试试图进行一种双向测试,从模型到模型。所以我将它重构了一下,以更好地适应您的示例。

import { Component } from '@angular/core'; 
import { TestBed, getTestBed, async } from '@angular/core/testing'; 
import { FormsModule } from '@angular/forms'; 
import { By } from '@angular/platform-browser'; 
import { dispatchEvent } from '@angular/platform-browser/testing/browser_util'; 

@Component({ 
    selector: 'ng-model-select-form', 
    template: ` 
    <select [(ngModel)]="selectedCity" (ngModelChange)="onSelected($event)"> 
     <option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option> 
    </select> 
    ` 
}) 
class NgModelSelectForm { 
    selectedCity: {[k: string]: string} = {}; 
    cities: any[] = []; 

    onSelected(value) { 
    } 
} 

describe('component: NgModelSelectForm',() => { 
    beforeEach(() => { 
    TestBed.configureTestingModule({ 
     imports: [ FormsModule ], 
     declarations: [ NgModelSelectForm ] 
    }); 
    }); 

    it('should go from model to change event', async(() => { 
    const fixture = TestBed.createComponent(NgModelSelectForm); 
    const comp = fixture.componentInstance; 
    spyOn(comp, 'onSelected'); 
    comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}]; 
    comp.selectedCity = comp.cities[1]; 
    fixture.detectChanges(); 
    const select = fixture.debugElement.query(By.css('select')); 

    fixture.whenStable().then(() => { 
     dispatchEvent(select.nativeElement, 'change'); 
     fixture.detectChanges(); 
     fixture.whenStable().then(() => { 
     expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'}); 
     console.log('after expect NYC'); 
     }); 
    }); 
    })); 
}); 
3

看看这个例子,从角源(template_integration_spec.ts)

@Component({ 
    selector: 'ng-model-select-form', 
    template: ` 
    <select [(ngModel)]="selectedCity"> 
     <option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option> 
    </select> 
    ` 
}) 
class NgModelSelectForm { 
    selectedCity: {[k: string]: string} = {}; 
    cities: any[] = []; 
} 



    it('with option values that are objects', fakeAsync(() => { 
     const fixture = TestBed.createComponent(NgModelSelectForm); 
     const comp = fixture.componentInstance; 
     comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}]; 
     comp.selectedCity = comp.cities[1]; 
     fixture.detectChanges(); 
     tick(); 

     const select = fixture.debugElement.query(By.css('select')); 
     const nycOption = fixture.debugElement.queryAll(By.css('option'))[1]; 

     // model -> view 
     expect(select.nativeElement.value).toEqual('1: Object'); 
     expect(nycOption.nativeElement.selected).toBe(true); 

     select.nativeElement.value = '2: Object'; 
     dispatchEvent(select.nativeElement, 'change'); 
     fixture.detectChanges(); 
     tick(); 

     // view -> model 
     expect(comp.selectedCity['name']).toEqual('Buffalo'); 
    })); 
+1

请解释为什么这个例子回答这个问题。 – Arashsoft

4

我发现peeskillet的回答非常有用的,但遗憾的是它是一个有点过时作为派遣一个事件的方式已经改变。我还发现有一个不必要的调用whenStable()。所以这是一个使用Peeskillet的设置更新测试:

it('should go from model to change event', async(() => { 
     const fixture = TestBed.createComponent(NgModelSelectForm); 
     const comp = fixture.componentInstance; 
     spyOn(comp, 'onSelected'); 
     comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}]; 
     comp.selectedCity = comp.cities[1]; 
     fixture.detectChanges(); 
     const select = fixture.debugElement.query(By.css('select')); 

     fixture.whenStable().then(() => { 
      select.nativeElement.dispatchEvent(new Event('change')); 
      fixture.detectChanges(); 
      expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'}); 
      console.log('after expect NYC'); 
     }); 
    }));