How to change value of a select box in angular2 unit test?

后端 未结 4 910
渐次进展
渐次进展 2020-12-29 05:34

I have an Angular2 component that contains a select box that looks like


                        
    
提交评论

  • 2020-12-29 06:11

    Look this example, from angular source (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');
         }));
    
    0 讨论(0)
  • 2020-12-29 06:21

    As far as the error. It seems like you just need to import By. This is not something that is global. It should be imported from the following module

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

    As far as the testing part, this is what I have been able to figure out. When you change a value in a the component, you need to trigger a change detection to update the view. You do this with fixture.detectChanges(). Once this is done, normally the view should be updated with the value.

    From testing something similar to your example, it seems this is not the case though. It seems there is still some asynchronous task going on after the change detection. Say we have the following

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

    This doesn't seem to work. It appears there is some async going on causing the value not to be set yet. So we need to wait for the async tasks by calling fixture.whenStable

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

    The above would work. But now we need to trigger the change event as that doesn't happen automatically.

    fixture.whenStable().then(() => {
      expect(select.nativeElement.value).toEqual('1: a value');
    
      dispatchEvent(select.nativeElement, 'change');
      fixture.detectChanges();
      fixture.whenStable().then(() => {
        // component expectations here
      });
    });
    

    Now we have another asynchronous task from the event. So we need to stabilize it again

    Below is a complete test that I tested with. It's a refactor of the example from the source code integration tests. They used fakeAsync and tick which is similar to using async and whenStable. But with fakeAsync, you can't use templateUrl, so I though it would be best to refactor it to use async.

    Also the source code tests does kind of a double one way testing, first testing model to view, then view to model. While it looks like your test was trying to do kind of a two-way test, from model around back to model. So I refactored it a bit to suite your example better.

    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');
          });
        });
      }));
    });
    
    0 讨论(0)
  • 2020-12-29 06:22

    I found peeskillet's answer very useful but sadly it is a little out of date as the way to dispatch an Event has been changed. I also found there was an unnecessary call to whenStable(). So here is an updated test using peeskillet's setup:

        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');
            });
        }));
    
    0 讨论(0)
  • 提交回复
    热议问题