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