Updating input html field from within an Angular 2 test

55,737

Solution 1

You're right that you can't just set the input, you also need to dispatch the 'input' event. Here is a function I wrote earlier this evening to input text:

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

Here fixture is the ComponentFixture and inputElement is the relevant HTTPInputElement from the fixture's nativeElement. This returns a promise, so you'll probably have to resolve it sendInput('whatever').then(...).

In context: https://github.com/textbook/known-for-web/blob/52c8aec4c2699c2f146a33c07786e1e32891c8b6/src/app/actor/actor.component.spec.ts#L134


Update:

We had some issues getting this to work in Angular 2.1, it didn't like creating a new Event(...), so instead we did:

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

Solution 2

The accepted solution didn't quite work for me in Angular 2.4. The value I had set was not appearing in the (test) UI, even after detectChanges() was called.

The way I got it to work was to set up my test as follows:

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

My TemplateComponent component has a property named personName in this example, which was the model property I am binding to in my template:

<input id="test2" type="text" [(ngModel)]="personName" />

Solution 3

I also had trouble getting jonrsharpe's answer to work with Angular 2.4. I found that the calls to fixture.detectChanges() and fixture.whenStable() caused the form component to reset. It seems that some initialization function is still pending when the test starts. I solved this by adding extra calls to these methods before each test. Here is a snippet of my code:

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...
    });
}));
Share:
55,737
Daniel
Author by

Daniel

Updated on July 21, 2022

Comments

  • Daniel
    Daniel almost 2 years

    I would like to change the value of an input field from within an Angular 2 unit test.

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

    I can't just change the ngModel because abc object is private:

     private abc: Abc = new Abc();
    

    In Angular 2 testing, can I simulate the user typing into the input field so that the ngModel will be updated with what the user has typed from within a unit test?

    I can grab the DebugElement and the nativeElement of the input field without a problem. (Just setting a the value property on the nativeElement of the input field doesn't seem to work as it doesn't update the ngModel with what I've set for the value).

    Maybe inputDebugEl.triggerEventHandler can be called, but I'm not sure what arguments to give it so it will simulate the user having typed a particular string of input.